diff --git a/game/Communication.js b/game/Communication.js new file mode 100644 index 0000000..fe6c1cc --- /dev/null +++ b/game/Communication.js @@ -0,0 +1,49 @@ +import WsClient from './WsClient.js' + +let conn +let changesCallback = () => {} + +function onChanges(callback) { + changesCallback = callback +} + +function connect(gameId, playerId) { + conn = new WsClient(WS_ADDRESS, playerId + '|' + gameId) + return new Promise(r => { + conn.connect() + conn.send(JSON.stringify({ type: 'init' })) + conn.onSocket('message', async ({ data }) => { + const d = JSON.parse(data) + if (d.type === 'init') { + r(d.game) + } else if (d.type === 'state_changed' && d.origin !== playerId) { + changesCallback(d.changes) + } + }) + }) +} + +const _STATE = { + changed: false, + changes: [], +} + +function addChange(change) { + _STATE.changes.push(change) + _STATE.changed = true +} + +function sendChanges() { + if (_STATE.changed) { + conn.send(JSON.stringify({ type: 'state', state: _STATE })) + _STATE.changes = [] + _STATE.changed = false + } +} + +export default { + connect, + onChanges, + addChange, + sendChanges, +} diff --git a/game/Debug.js b/game/Debug.js new file mode 100644 index 0000000..c7b47be --- /dev/null +++ b/game/Debug.js @@ -0,0 +1,21 @@ +let _pt = 0 +let _mindiff = 0 + +const checkpoint_start = (mindiff) => { + _pt = performance.now() + _mindiff = mindiff +} + +const checkpoint = (label) => { + const now = performance.now(); + const diff = now - _pt + if (diff > _mindiff) { + console.log(label + ': ' + (diff)); + } + _pt = now; +} + +export default { + checkpoint_start, + checkpoint, +} diff --git a/game/index.js b/game/index.js index 213e955..07d6e2f 100644 --- a/game/index.js +++ b/game/index.js @@ -7,9 +7,13 @@ import Camera from './Camera.js' import EventAdapter from './EventAdapter.js' import WsClient from './WsClient.js' import Graphics from './Graphics.js' +import Debug from './Debug.js' +import Communication from './Communication.js' -if (!GAME_ID) throw '[ GAME_ID not set ]' -if (!WS_ADDRESS) throw '[ WS_ADDRESS not set ]' +if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]' +if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]' + +if (typeof DEBUG === 'undefined') window.DEBUG = false function addCanvasToDom(canvas) { document.body.append(canvas) @@ -178,6 +182,33 @@ const unfinishedTileByPos = (puzzle, pos) => { return tileIdx } +class DirtyRect { + constructor() { + this.reset() + } + get () { + return this.x0 === null ? null : [ + {x0: this.x0, x1: this.x1, y0: this.y0, y1: this.y1} + ] + } + add (pos, offset) { + const x0 = pos.x - offset + const x1 = pos.x + offset + const y0 = pos.y - offset + const y1 = pos.y + offset + this.x0 = this.x0 === null ? x0 : Math.min(this.x0, x0) + this.x1 = this.x1 === null ? x1 : Math.max(this.x1, x1) + this.y0 = this.y0 === null ? y0 : Math.min(this.y0, y0) + this.y1 = this.y1 === null ? y1 : Math.max(this.y1, y1) + } + reset () { + this.x0 = null + this.x1 = null + this.y0 = null + this.y1 = null + } +} + async function loadPuzzleBitmaps(puzzle) { // load bitmap, to determine the original size of the image const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl) @@ -203,12 +234,6 @@ function initme() { return ID } -function setupNetwork(me) { - const wsc = new WsClient(WS_ADDRESS, me) - wsc.connect() - return wsc -} - async function main () { let gameId = GAME_ID let me = initme() @@ -216,569 +241,503 @@ async function main () { let cursorGrab = await Graphics.loadImageToBitmap('/grab.png') let cursorHand = await Graphics.loadImageToBitmap('/hand.png') - let conn = setupNetwork(me + '|' + gameId) - conn.send(JSON.stringify({ type: 'init' })) - conn.onSocket('message', async ({data}) => { - const d = JSON.parse(data) - if (d.type === 'init') { - console.log('the game ', d.game) - let bitmaps = await loadPuzzleBitmaps(d.game.puzzle) - startGame(d.game, bitmaps, conn) - } else { - // console.log(d) + const game = await Communication.connect(gameId, me) + + const bitmaps = await loadPuzzleBitmaps(game.puzzle) + const puzzle = game.puzzle + const players = game.players + + // information for next render cycle + let rectPlayer = new DirtyRect() + let rerenderPlayer = true + let rectTable = new DirtyRect() + let rerenderTable = true + let rerender = true + + const changePlayer = (change) => { + for (let k of Object.keys(change)) { + players[me][k] = change[k] + } + Communication.addChange({type: 'change_player', player: players[me]}) + } + const changeData = (change) => { + for (let k of Object.keys(change)) { + puzzle.data[k] = change[k] + } + Communication.addChange({type: 'change_data', data: puzzle.data}) + } + const changeTile = (t, change) => { + for (let k of Object.keys(change)) { + t[k] = change[k] + } + Communication.addChange({type: 'change_tile', tile: t}) + } + + // Create a dom and attach adapters to it so we can work with it + const canvas = addCanvasToDom(Graphics.createCanvas()) + const adapter = new CanvasAdapter(canvas) + const evts = new EventAdapter(canvas) + + // initialize some view data + // this global data will change according to input events + const viewport = new Camera(canvas) + + Communication.onChanges((changes) => { + for (let change of changes) { + switch (change.type) { + case 'change_player': { + if (players[change.player.id]) { + rectPlayer.add(viewport.worldToViewport(players[change.player.id]), cursorGrab.width) + } + + players[change.player.id] = change.player + + rectPlayer.add(viewport.worldToViewport(players[change.player.id]), cursorGrab.width) + } break; + + case 'change_tile': { + rectTable.add(puzzle.tiles[change.tile.idx].pos, puzzle.info.tileDrawSize) + + puzzle.tiles[change.tile.idx] = change.tile + + rectTable.add(puzzle.tiles[change.tile.idx].pos, puzzle.info.tileDrawSize) + } break; + case 'change_data': { + puzzle.data = change.data + } break; + } } }) - const _STATE = { - changes: [], - } - let _STATE_CHANGED = false + // Information about what tile is the player currently grabbing + let grabbingTileIdx = -1 - class renderRect { - constructor() { - this.reset() + // The actual place for the puzzle. The tiles may + // not be moved around infinitely, just on the (invisible) + // puzzle table. however, the camera may move away from the table + const puzzleTableColor = [40, 40, 40, 0] + const puzzleTable = new Bitmap( + puzzle.info.table.width, + puzzle.info.table.height, + puzzleTableColor + ) + + // In the middle of the table, there is a board. this is to + // tell the player where to place the final puzzle + const boardColor = [80, 80, 80, 255] + const board = new Bitmap( + puzzle.info.width, + puzzle.info.height, + boardColor + ) + const boardPos = { + x: (puzzleTable.width - board.width) / 2, + y: (puzzleTable.height - board.height) / 2 + } // relative to table. + + + // Some helper functions for working with the grabbing and snapping + // --------------------------------------------------------------- + + // get all grouped tiles for a tile + function getGroupedTiles(tile) { + let grouped = [] + if (tile.group) { + for (let other of puzzle.tiles) { + if (other.group === tile.group) { + grouped.push(other) + } + } + } else { + grouped.push(tile) } - get () { - return this.x0 === null ? null : [ - {x0: this.x0, x1: this.x1, y0: this.y0, y1: this.y1} - ] + return grouped + } + + // put both tiles (and their grouped tiles) in the same group + const groupTiles = (tile, other) => { + let targetGroup + let searchGroups = [] + if (tile.group) { + searchGroups.push(tile.group) } - add (pos, offset) { - const x0 = pos.x - offset - const x1 = pos.x + offset - const y0 = pos.y - offset - const y1 = pos.y + offset - this.x0 = this.x0 === null ? x0 : Math.min(this.x0, x0) - this.x1 = this.x1 === null ? x1 : Math.max(this.x1, x1) - this.y0 = this.y0 === null ? y0 : Math.min(this.y0, y0) - this.y1 = this.y1 === null ? y1 : Math.max(this.y1, y1) + if (other.group) { + searchGroups.push(other.group) } - reset () { - this.x0 = null - this.x1 = null - this.y0 = null - this.y1 = null + if (tile.group) { + targetGroup = tile.group + } else if (other.group) { + targetGroup = other.group + } else { + changeData({ maxGroup: puzzle.data.maxGroup + 1 }) + targetGroup = puzzle.data.maxGroup + } + + changeTile(tile, { group: targetGroup }) + changeTile(other, { group: targetGroup }) + + if (searchGroups.length > 0) { + for (let tmp of puzzle.tiles) { + if (searchGroups.includes(tmp.group)) { + changeTile(tmp, { group: targetGroup }) + } + } } } - const startGame = (game, bitmaps, conn) => { - let puzzle = game.puzzle - let players = game.players - // information for next render cycle - let rectPlayer = new renderRect() - let rerenderPlayer = true - let rectTable = new renderRect() - let rerenderTable = true - let rerender = true + // determine if two tiles are grouped together + const areGrouped = (t1, t2) => { + return t1.group && t1.group === t2.group + } - const changePlayer = (change) => { - for (let k of Object.keys(change)) { - players[me][k] = change[k] - } - _STATE.changes.push({type: 'change_player', player: players[me]}) - _STATE_CHANGED = true - } - const changeData = (change) => { - for (let k of Object.keys(change)) { - puzzle.data[k] = change[k] - } - _STATE.changes.push({type: 'change_data', data: puzzle.data}) - _STATE_CHANGED = true - } + // get the center position of a tile + const tileCenterPos = (tile) => { + return tileRectByTile(tile).center() + } - const changeTile = (t, change) => { - for (let k of Object.keys(change)) { - t[k] = change[k] - } - _STATE.changes.push({type: 'change_tile', tile: t}) - _STATE_CHANGED = true - } - - // Create a dom and attach adapters to it so we can work with it - const canvas = addCanvasToDom(Graphics.createCanvas()) - const adapter = new CanvasAdapter(canvas) - const evts = new EventAdapter(canvas) - - // initialize some view data - // this global data will change according to input events - const viewport = new Camera(canvas) - - conn.onSocket('message', ({data}) => { - const d = JSON.parse(data) - if (d.type === 'state_changed' && d.origin !== me) { - for (let change of d.changes) { - switch (change.type) { - case 'change_player': { - if (players[change.player.id]) { - rectPlayer.add(viewport.worldToViewport(players[change.player.id]), cursorGrab.width) - } - - players[change.player.id] = change.player - - rectPlayer.add(viewport.worldToViewport(players[change.player.id]), cursorGrab.width) - } break; - - case 'change_tile': { - rectTable.add(puzzle.tiles[change.tile.idx].pos, puzzle.info.tileDrawSize) - - puzzle.tiles[change.tile.idx] = change.tile - - rectTable.add(puzzle.tiles[change.tile.idx].pos, puzzle.info.tileDrawSize) - } break; - case 'change_data': { - puzzle.data = change.data - } break; - } - } - } - }) - - // Information about what tile is the player currently grabbing - let grabbingTileIdx = -1 - - // The actual place for the puzzle. The tiles may - // not be moved around infinitely, just on the (invisible) - // puzzle table. however, the camera may move away from the table - const puzzleTableColor = [40, 40, 40, 0] - const puzzleTable = new Bitmap( - puzzle.info.table.width, - puzzle.info.table.height, - puzzleTableColor + // get the would-be visible bounding rect if a tile was + // in given position + const tileRectByPos = (pos) => { + return new BoundingRectangle( + pos.x, + pos.x + puzzle.info.tileSize, + pos.y, + pos.y + puzzle.info.tileSize ) + } - // In the middle of the table, there is a board. this is to - // tell the player where to place the final puzzle - const boardColor = [80, 80, 80, 255] - const board = new Bitmap( - puzzle.info.width, - puzzle.info.height, - boardColor - ) - const boardPos = { - x: (puzzleTable.width - board.width) / 2, - y: (puzzleTable.height - board.height) / 2 - } // relative to table. + // get the current visible bounding rect for a tile + const tileRectByTile = (tile) => { + return tileRectByPos(tile.pos) + } + + const tilesSortedByZIndex = () => { + const sorted = puzzle.tiles.slice() + return sorted.sort((t1, t2) => t1.z - t2.z) + } + + const setGroupedZIndex = (tile, zIndex) => { + for (let t of getGroupedTiles(tile)) { + changeTile(t, { z: zIndex }) + } + } + + const setGroupedOwner = (tile, owner) => { + for (let t of getGroupedTiles(tile)) { + // may only change own tiles or untaken tiles + if (t.owner === me || t.owner === 0) { + changeTile(t, { owner: owner }) + } + } + } + + const moveGroupedTilesDiff = (tile, diffX, diffY) => { + for (let t of getGroupedTiles(tile)) { + changeTile(t, { pos: pointAdd(t.pos, { x: diffX, y: diffY }) }) + + // TODO: instead there could be a function to + // get min/max x/y of a group + rectTable.add(tileCenterPos(t), puzzle.info.tileDrawSize) + } + } + const moveGroupedTiles = (tile, dst) => { + let diff = pointSub(tile.pos, dst) + moveGroupedTilesDiff(tile, -diff.x, -diff.y) + } + const finishGroupedTiles = (tile) => { + for (let t of getGroupedTiles(tile)) { + changeTile(t, { owner: -1, z: 1 }) + } + } + // --------------------------------------------------------------- - // Some helper functions for working with the grabbing and snapping - // --------------------------------------------------------------- - // get all grouped tiles for a tile - function getGroupedTiles(tile) { - let grouped = [] - if (tile.group) { - for (let other of puzzle.tiles) { - if (other.group === tile.group) { - grouped.push(other) - } + + + + + let _last_mouse = null + let _last_mouse_down = null + const onUpdate = () => { + let last_x = null + let last_y = null + + if (_last_mouse_down !== null) { + last_x = _last_mouse_down.x + last_y = _last_mouse_down.y + } + for (let mouse of evts.consumeAll()) { + const tp = viewport.viewportToWorld(mouse) + if (mouse.type === 'move') { + changePlayer({ x: tp.x, y: tp.y }) + if (_last_mouse) { + rectPlayer.add(_last_mouse, cursorGrab.width) } - } else { - grouped.push(tile) - } - return grouped - } - - // put both tiles (and their grouped tiles) in the same group - const groupTiles = (tile, other) => { - let targetGroup - let searchGroups = [] - if (tile.group) { - searchGroups.push(tile.group) - } - if (other.group) { - searchGroups.push(other.group) - } - if (tile.group) { - targetGroup = tile.group - } else if (other.group) { - targetGroup = other.group - } else { - changeData({ maxGroup: puzzle.data.maxGroup + 1 }) - targetGroup = puzzle.data.maxGroup - } - - changeTile(tile, { group: targetGroup }) - changeTile(other, { group: targetGroup }) - - if (searchGroups.length > 0) { - for (let tmp of puzzle.tiles) { - if (searchGroups.includes(tmp.group)) { - changeTile(tmp, { group: targetGroup }) - } - } - } - } - - // determine if two tiles are grouped together - const areGrouped = (t1, t2) => { - return t1.group && t1.group === t2.group - } - - // get the center position of a tile - const tileCenterPos = (tile) => { - return tileRectByTile(tile).center() - } - - // get the would-be visible bounding rect if a tile was - // in given position - const tileRectByPos = (pos) => { - return new BoundingRectangle( - pos.x, - pos.x + puzzle.info.tileSize, - pos.y, - pos.y + puzzle.info.tileSize - ) - } - - // get the current visible bounding rect for a tile - const tileRectByTile = (tile) => { - return tileRectByPos(tile.pos) - } - - const tilesSortedByZIndex = () => { - const sorted = puzzle.tiles.slice() - return sorted.sort((t1, t2) => t1.z - t2.z) - } - - const setGroupedZIndex = (tile, zIndex) => { - for (let t of getGroupedTiles(tile)) { - changeTile(t, { z: zIndex }) - } - } - - const setGroupedOwner = (tile, owner) => { - for (let t of getGroupedTiles(tile)) { - // may only change own tiles or untaken tiles - if (t.owner === me || t.owner === 0) { - changeTile(t, { owner: owner }) - } - } - } - - const moveGroupedTilesDiff = (tile, diffX, diffY) => { - for (let t of getGroupedTiles(tile)) { - changeTile(t, { pos: pointAdd(t.pos, { x: diffX, y: diffY }) }) - - // TODO: instead there could be a function to - // get min/max x/y of a group - rectTable.add(tileCenterPos(t), puzzle.info.tileDrawSize) - } - } - const moveGroupedTiles = (tile, dst) => { - let diff = pointSub(tile.pos, dst) - moveGroupedTilesDiff(tile, -diff.x, -diff.y) - } - const finishGroupedTiles = (tile) => { - for (let t of getGroupedTiles(tile)) { - changeTile(t, { owner: -1, z: 1 }) - } - } - // --------------------------------------------------------------- - - - - - - - - let _last_mouse = null - let _last_mouse_down = null - const onUpdate = () => { - let last_x = null - let last_y = null - - if (_last_mouse_down !== null) { - last_x = _last_mouse_down.x - last_y = _last_mouse_down.y - } - for (let mouse of evts.consumeAll()) { - const tp = viewport.viewportToWorld(mouse) - if (mouse.type === 'move') { - changePlayer({ x: tp.x, y: tp.y }) - if (_last_mouse) { - rectPlayer.add(_last_mouse, cursorGrab.width) - } - rectPlayer.add(mouse, cursorGrab.width) - - if (_last_mouse_down !== null) { - _last_mouse_down = mouse - - if (last_x === null || last_y === null) { - last_x = mouse.x - last_y = mouse.y - } - - if (grabbingTileIdx >= 0) { - let tp_last = viewport.viewportToWorld({ x: last_x, y: last_y }) - const diffX = tp.x - tp_last.x - const diffY = tp.y - tp_last.y - - let t = puzzle.tiles[grabbingTileIdx] - moveGroupedTilesDiff(t, diffX, diffY) - - // todo: dont +- tileDrawSize, we can work with less? - rectTable.add(tp, puzzle.info.tileDrawSize) - rectTable.add(tp_last, puzzle.info.tileDrawSize) - } else { - // move the cam - const diffX = Math.round(mouse.x - last_x) - const diffY = Math.round(mouse.y - last_y) - viewport.move(diffX, diffY) - rerender = true - } - } - } else if (mouse.type === 'down') { - changePlayer({ down: true }) - rectPlayer.add(mouse, cursorGrab.width) + rectPlayer.add(mouse, cursorGrab.width) + if (_last_mouse_down !== null) { _last_mouse_down = mouse + if (last_x === null || last_y === null) { last_x = mouse.x last_y = mouse.y } - grabbingTileIdx = unfinishedTileByPos(puzzle, tp) - console.log(grabbingTileIdx) if (grabbingTileIdx >= 0) { - changeData({ maxZ: puzzle.data.maxZ + 1 }) - setGroupedZIndex(puzzle.tiles[grabbingTileIdx], puzzle.data.maxZ) - setGroupedOwner(puzzle.tiles[grabbingTileIdx], me) - } - console.log('down', tp) + const tp_last = viewport.viewportToWorld({ x: last_x, y: last_y }) + const diffX = tp.x - tp_last.x + const diffY = tp.y - tp_last.y - } else if (mouse.type === 'up') { - changePlayer({ down: false }) + const t = puzzle.tiles[grabbingTileIdx] + moveGroupedTilesDiff(t, diffX, diffY) + + // todo: dont +- tileDrawSize, we can work with less? + rectTable.add(tp, puzzle.info.tileDrawSize) + rectTable.add(tp_last, puzzle.info.tileDrawSize) + } else { + // move the cam + const diffX = Math.round(mouse.x - last_x) + const diffY = Math.round(mouse.y - last_y) + viewport.move(diffX, diffY) + rerender = true + } + } + } else if (mouse.type === 'down') { + changePlayer({ down: true }) + rectPlayer.add(mouse, cursorGrab.width) + + _last_mouse_down = mouse + if (last_x === null || last_y === null) { + last_x = mouse.x + last_y = mouse.y + } + + grabbingTileIdx = unfinishedTileByPos(puzzle, tp) + console.log(grabbingTileIdx) + if (grabbingTileIdx >= 0) { + changeData({ maxZ: puzzle.data.maxZ + 1 }) + setGroupedZIndex(puzzle.tiles[grabbingTileIdx], puzzle.data.maxZ) + setGroupedOwner(puzzle.tiles[grabbingTileIdx], me) + } + + } else if (mouse.type === 'up') { + changePlayer({ down: false }) + if (_last_mouse) { + rectPlayer.add(_last_mouse, cursorGrab.width) + } + rectPlayer.add(mouse, cursorGrab.width) + + _last_mouse_down = null + last_x = null + last_y === null + + if (grabbingTileIdx >= 0) { + // Check if the tile was dropped at the correct + // location + + let tile = puzzle.tiles[grabbingTileIdx] + setGroupedOwner(tile, 0) + let pt = pointSub(tile.pos, boardPos) + let dst = tileRectByPos(pt) + let srcRect = srcRectByIdx(puzzle.info, grabbingTileIdx) + if (srcRect.centerDistance(dst) < puzzle.info.snapDistance) { + // Snap the tile to the final destination + moveGroupedTiles(tile, { + x: srcRect.x0 + boardPos.x, + y: srcRect.y0 + boardPos.y, + }) + finishGroupedTiles(tile) + rectTable.add(tp, puzzle.info.tileDrawSize) + } else { + // Snap to other tiles + const check = (t, off, other) => { + if (!other || (other.owner === -1) || areGrouped(t, other)) { + return false + } + let trec_ = tileRectByTile(t) + let otrec = tileRectByTile(other).moved( + off[0] * puzzle.info.tileSize, + off[1] * puzzle.info.tileSize + ) + if (trec_.centerDistance(otrec) < puzzle.info.snapDistance) { + moveGroupedTiles(t, { x: otrec.x0, y: otrec.y0 }) + groupTiles(t, other) + setGroupedZIndex(t, t.z) + rectTable.add(tileCenterPos(t), puzzle.info.tileDrawSize) + return true + } + return false + } + + for (let t of getGroupedTiles(tile)) { + let others = getSurroundingTilesByIdx(puzzle, t.idx) + if ( + check(t, [0, 1], others[0]) // top + || check(t, [-1, 0], others[1]) // right + || check(t, [0, -1], others[2]) // bottom + || check(t, [1, 0], others[3]) // left + ) { + break + } + } + } + } + grabbingTileIdx = -1 + } else if (mouse.type === 'wheel') { + if ( + mouse.deltaY < 0 && viewport.zoomIn() + || mouse.deltaY > 0 && viewport.zoomOut() + ) { + rerender = true + changePlayer({ x: tp.x, y: tp.y }) if (_last_mouse) { rectPlayer.add(_last_mouse, cursorGrab.width) } rectPlayer.add(mouse, cursorGrab.width) - - _last_mouse_down = null - last_x = null - last_y === null - - if (grabbingTileIdx >= 0) { - // Check if the tile was dropped at the correct - // location - - let tile = puzzle.tiles[grabbingTileIdx] - setGroupedOwner(tile, 0) - let pt = pointSub(tile.pos, boardPos) - let dst = tileRectByPos(pt) - let srcRect = srcRectByIdx(puzzle.info, grabbingTileIdx) - if (srcRect.centerDistance(dst) < puzzle.info.snapDistance) { - // Snap the tile to the final destination - console.log('ok! !!!') - moveGroupedTiles(tile, { - x: srcRect.x0 + boardPos.x, - y: srcRect.y0 + boardPos.y, - }) - finishGroupedTiles(tile) - rectTable.add(tp, puzzle.info.tileDrawSize) - } else { - // Snap to other tiles - const check = (t, off, other) => { - if (!other || (other.owner === -1) || areGrouped(t, other)) { - return false - } - let trec_ = tileRectByTile(t) - let otrec = tileRectByTile(other).moved( - off[0] * puzzle.info.tileSize, - off[1] * puzzle.info.tileSize - ) - if (trec_.centerDistance(otrec) < puzzle.info.snapDistance) { - moveGroupedTiles(t, { x: otrec.x0, y: otrec.y0 }) - groupTiles(t, other) - setGroupedZIndex(t, t.z) - rectTable.add(tileCenterPos(t), puzzle.info.tileDrawSize) - return true - } - return false - } - - for (let t of getGroupedTiles(tile)) { - let others = getSurroundingTilesByIdx(puzzle, t.idx) - if ( - check(t, [0, 1], others[0]) // top - || check(t, [-1, 0], others[1]) // right - || check(t, [0, -1], others[2]) // bottom - || check(t, [1, 0], others[3]) // left - ) { - break - } - } - } - } - grabbingTileIdx = -1 - console.log('up', tp) - } else if (mouse.type === 'wheel') { - if ( - mouse.deltaY < 0 && viewport.zoomIn() - || mouse.deltaY > 0 && viewport.zoomOut() - ) { - rerender = true - changePlayer({ x: tp.x, y: tp.y }) - if (_last_mouse) { - rectPlayer.add(_last_mouse, cursorGrab.width) - } - rectPlayer.add(mouse, cursorGrab.width) - } } - // console.log(mouse) - _last_mouse = mouse - } - if (rectTable.get()) { - rerenderTable = true - } - if (rectPlayer.get()) { - rerenderPlayer = true - } - - if (_STATE_CHANGED) { - conn.send(JSON.stringify({ - type: 'state', - state: _STATE, - })) - _STATE.changes = [] - _STATE_CHANGED = false } + // console.log(mouse) + _last_mouse = mouse + } + if (rectTable.get()) { + rerenderTable = true + } + if (rectPlayer.get()) { + rerenderPlayer = true } - - // helper for measuring performance - let _pt = 0 - let _mindiff = 0 - const checkpoint_start = (mindiff) => { - _pt = performance.now() - _mindiff = mindiff - } - const checkpoint = (n) => { - const now = performance.now(); - const diff = now - _pt - if (diff > _mindiff) { - console.log(n + ': ' + (diff)); - } - _pt = now; - } - - // TODO: - // try out layered rendering and see - // if it improves performance: - // 1. background - // 2. tiles - // 3. (moving tiles) - // 4. (players) - // (currently, if a player moves, everthing needs to be - // rerendered at that position manually, maybe it is faster - // when using layers) - const onRender = () => { - if (!rerenderTable && !rerenderPlayer && !rerender) { - return - } - - checkpoint_start(20) - - // draw the puzzle table - if (rerenderTable) { - - Graphics.fillBitmapCapped(puzzleTable, puzzleTableColor, rectTable.get()) - checkpoint('after fill') - - // draw the puzzle board on the table - Graphics.mapBitmapToBitmapCapped(board, board.getBoundingRect(), puzzleTable, new BoundingRectangle( - boardPos.x, - boardPos.x + board.width - 1, - boardPos.y, - boardPos.y + board.height - 1, - ), rectTable.get()) - checkpoint('imgtoimg') - - // draw all the tiles on the table - - for (let tile of tilesSortedByZIndex()) { - let rect = new BoundingRectangle( - puzzle.info.tileDrawOffset + tile.pos.x, - puzzle.info.tileDrawOffset + tile.pos.x + puzzle.info.tileDrawSize, - puzzle.info.tileDrawOffset + tile.pos.y, - puzzle.info.tileDrawOffset + tile.pos.y + puzzle.info.tileDrawSize, - ) - let bmp = bitmaps[tile.idx] - Graphics.mapBitmapToBitmapCapped( - bmp, - bmp.getBoundingRect(), - puzzleTable, - rect, - rectTable.get() - ) - } - checkpoint('tiles') - } - - if (rerenderTable || rerender) { - // finally draw the finished table onto the canvas - // only part of the table may be visible, depending on the - // camera - adapter.clear() - adapter.apply() - checkpoint('afterclear_1') - - // TODO: improve the rendering - // atm it is pretty slow (~40-50ms) - Graphics.mapBitmapToAdapter( - puzzleTable, - viewport.rect(), - adapter, - adapter.getBoundingRect() - ) - checkpoint('to_adapter_1') - } else if (rerenderPlayer) { - adapter.clearRect(rectPlayer.get()) - checkpoint('afterclear_2') - Graphics.mapBitmapToAdapterCapped( - puzzleTable, - viewport.rect(), - adapter, - adapter.getBoundingRect(), - rectPlayer.get() - ) - checkpoint('to_adapter_2') - } - - if (rerenderPlayer) { - for (let id of Object.keys(players)) { - let p = players[id] - let cursor = p.down ? cursorGrab : cursorHand - let back = viewport.worldToViewport(p) - Graphics.mapBitmapToAdapter( - cursor, - cursor.getBoundingRect(), - adapter, - new BoundingRectangle( - back.x - (cursor.width / 2), - back.x - (cursor.width / 2) + cursor.width - 1, - back.y - (cursor.width / 2), - back.y - (cursor.width / 2) + cursor.height - 1, - ) - ) - } - checkpoint('after_players') - } - - adapter.apply() - checkpoint('finals') - - rerenderTable = false - rerenderPlayer = false - rerender = false - rectTable.reset() - rectPlayer.reset() - } - - run({ - update: onUpdate, - render: onRender, - }) + Communication.sendChanges() } + + // TODO: + // try out layered rendering and see + // if it improves performance: + // 1. background + // 2. tiles + // 3. (moving tiles) + // 4. (players) + // (currently, if a player moves, everthing needs to be + // rerendered at that position manually, maybe it is faster + // when using layers) + const onRender = () => { + if (!rerenderTable && !rerenderPlayer && !rerender) { + return + } + + if (DEBUG) Debug.checkpoint_start(20) + + // draw the puzzle table + if (rerenderTable) { + + Graphics.fillBitmapCapped(puzzleTable, puzzleTableColor, rectTable.get()) + + if (DEBUG) Debug.checkpoint('after fill') + + // draw the puzzle board on the table + Graphics.mapBitmapToBitmapCapped(board, board.getBoundingRect(), puzzleTable, new BoundingRectangle( + boardPos.x, + boardPos.x + board.width - 1, + boardPos.y, + boardPos.y + board.height - 1, + ), rectTable.get()) + + if (DEBUG) Debug.checkpoint('imgtoimg') + + // draw all the tiles on the table + + for (let tile of tilesSortedByZIndex()) { + let rect = new BoundingRectangle( + puzzle.info.tileDrawOffset + tile.pos.x, + puzzle.info.tileDrawOffset + tile.pos.x + puzzle.info.tileDrawSize, + puzzle.info.tileDrawOffset + tile.pos.y, + puzzle.info.tileDrawOffset + tile.pos.y + puzzle.info.tileDrawSize, + ) + let bmp = bitmaps[tile.idx] + Graphics.mapBitmapToBitmapCapped( + bmp, + bmp.getBoundingRect(), + puzzleTable, + rect, + rectTable.get() + ) + } + + if (DEBUG) Debug.checkpoint('tiles') + } + + if (rerenderTable || rerender) { + // finally draw the finished table onto the canvas + // only part of the table may be visible, depending on the + // camera + adapter.clear() + adapter.apply() + + if (DEBUG) Debug.checkpoint('afterclear_1') + + // TODO: improve the rendering + // atm it is pretty slow (~40-50ms) + Graphics.mapBitmapToAdapter( + puzzleTable, + viewport.rect(), + adapter, + adapter.getBoundingRect() + ) + + if (DEBUG) Debug.checkpoint('to_adapter_1') + + } else if (rerenderPlayer) { + adapter.clearRect(rectPlayer.get()) + + if (DEBUG) Debug.checkpoint('afterclear_2') + + Graphics.mapBitmapToAdapterCapped( + puzzleTable, + viewport.rect(), + adapter, + adapter.getBoundingRect(), + rectPlayer.get() + ) + + if (DEBUG) Debug.checkpoint('to_adapter_2') + } + + if (rerenderPlayer) { + for (let id of Object.keys(players)) { + let p = players[id] + let cursor = p.down ? cursorGrab : cursorHand + let back = viewport.worldToViewport(p) + Graphics.mapBitmapToAdapter( + cursor, + cursor.getBoundingRect(), + adapter, + new BoundingRectangle( + back.x - (cursor.width / 2), + back.x - (cursor.width / 2) + cursor.width - 1, + back.y - (cursor.width / 2), + back.y - (cursor.width / 2) + cursor.height - 1, + ) + ) + } + + if (DEBUG) Debug.checkpoint('after_players') + } + + adapter.apply() + + if (DEBUG) Debug.checkpoint('finals') + + rerenderTable = false + rerenderPlayer = false + rerender = false + rectTable.reset() + rectPlayer.reset() + } + + run({ + update: onUpdate, + render: onRender, + }) } main()