puzzle/game/index.js

704 lines
21 KiB
JavaScript
Raw Normal View History

2020-11-07 11:35:29 +01:00
"use strict"
import {run} from './gameloop.js'
import Camera from './Camera.js'
import EventAdapter from './EventAdapter.js'
2020-11-09 00:29:07 +01:00
import Graphics from './Graphics.js'
2020-11-09 01:54:05 +01:00
import Debug from './Debug.js'
import Communication from './Communication.js'
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]'
2020-11-10 19:09:48 +01:00
if (typeof DEBUG === 'undefined') window.DEBUG = false
2020-11-07 12:21:38 +01:00
2020-11-07 11:35:29 +01:00
function addCanvasToDom(canvas) {
document.body.append(canvas)
return canvas
}
2020-11-10 18:48:16 +01:00
function pointDistance(a, b) {
const diffX = a.x - b.x
const diffY = a.y - b.y
return Math.sqrt(diffX * diffX + diffY * diffY)
}
function rectMoved(rect, x, y) {
return {
x: rect.x + x,
y: rect.y + y,
w: rect.w,
h: rect.h,
}
}
const rectCenter = (rect) => {
return {
x: rect.x + (rect.w / 2),
y: rect.y + (rect.h / 2),
}
}
function rectCenterDistance(a, b) {
return pointDistance(rectCenter(a), rectCenter(b))
}
2020-11-08 12:56:54 +01:00
function pointInBounds(pt, rect) {
2020-11-10 18:48:16 +01:00
return pt.x >= rect.x
&& pt.x <= rect.x + rect.w
&& pt.y >= rect.y
&& pt.y <= rect.y + rect.h
2020-11-07 11:35:29 +01:00
}
function getSurroundingTilesByIdx(puzzle, idx) {
2020-11-08 12:58:21 +01:00
var _X = puzzle.info.coords[idx].x
var _Y = puzzle.info.coords[idx].y
return [
// top
_Y === 0 ? null : puzzle.tiles[idx - puzzle.info.tiles_x],
// right
(_X === puzzle.info.tiles_x - 1) ? null : puzzle.tiles[idx + 1],
// bottom
(_Y === puzzle.info.tiles_y - 1) ? null : puzzle.tiles[idx + puzzle.info.tiles_x],
// left
_X === 0 ? null : puzzle.tiles[idx - 1]
]
2020-11-07 11:35:29 +01:00
}
2020-11-10 18:48:16 +01:00
async function createPuzzleTileBitmaps(img, tiles, info) {
2020-11-08 12:58:21 +01:00
var tileSize = info.tileSize
var tileMarginWidth = info.tileMarginWidth
var tileDrawSize = info.tileDrawSize
var tileRatio = tileSize / 100.0
var curvyCoords = [
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 bitmaps = new Array(tiles.length)
const paths = {}
function pathForShape(shape) {
const key = `${shape.top}${shape.right}${shape.left}${shape.bottom}`
if (paths[key]) {
return paths[key]
2020-11-08 12:56:54 +01:00
}
2020-11-07 11:35:29 +01:00
2020-11-08 12:58:21 +01:00
const path = new Path2D()
const topLeftEdge = { x: tileMarginWidth, y: tileMarginWidth }
path.moveTo(topLeftEdge.x, topLeftEdge.y)
for (let i = 0; i < curvyCoords.length / 6; i++) {
const p1 = pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.top * curvyCoords[i * 6 + 1] * tileRatio })
const p2 = pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.top * curvyCoords[i * 6 + 3] * tileRatio })
const p3 = pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.top * curvyCoords[i * 6 + 5] * tileRatio })
path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
}
const topRightEdge = pointAdd(topLeftEdge, { x: tileSize, y: 0 })
for (let i = 0; i < curvyCoords.length / 6; i++) {
const p1 = pointAdd(topRightEdge, { x: -shape.right * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio })
const p2 = pointAdd(topRightEdge, { x: -shape.right * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio })
const p3 = pointAdd(topRightEdge, { x: -shape.right * 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);
}
const bottomRightEdge = pointAdd(topRightEdge, { x: 0, y: tileSize })
for (let i = 0; i < curvyCoords.length / 6; i++) {
let p1 = pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio })
let p2 = pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio })
let p3 = 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);
}
const bottomLeftEdge = pointSub(bottomRightEdge, { x: tileSize, y: 0 })
for (let i = 0; i < curvyCoords.length / 6; i++) {
let p1 = pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio })
let p2 = pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio })
let p3 = 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);
2020-11-07 11:35:29 +01:00
}
2020-11-08 12:58:21 +01:00
paths[key] = path
return path
}
2020-11-07 11:35:29 +01:00
2020-11-08 12:58:21 +01:00
for (let tile of tiles) {
const srcRect = srcRectByIdx(info, tile.idx)
const path = pathForShape(info.shapes[tile.idx])
2020-11-09 00:29:07 +01:00
const c = Graphics.createCanvas(tileDrawSize, tileDrawSize)
2020-11-08 12:58:21 +01:00
const ctx = c.getContext('2d')
// -----------------------------------------------------------
// -----------------------------------------------------------
ctx.lineWidth = 2
ctx.stroke(path)
// -----------------------------------------------------------
// -----------------------------------------------------------
ctx.save();
ctx.clip(path)
ctx.drawImage(
img,
2020-11-10 18:48:16 +01:00
srcRect.x - tileMarginWidth,
srcRect.y - tileMarginWidth,
2020-11-08 12:58:21 +01:00
tileDrawSize,
tileDrawSize,
0,
0,
tileDrawSize,
tileDrawSize,
)
ctx.stroke(path)
ctx.restore();
2020-11-10 18:48:16 +01:00
bitmaps[tile.idx] = await createImageBitmap(c)
2020-11-08 12:58:21 +01:00
}
return bitmaps
2020-11-07 11:35:29 +01:00
}
2020-11-08 12:58:21 +01:00
function srcRectByIdx(puzzleInfo, idx) {
let c = puzzleInfo.coords[idx]
let cx = c.x * puzzleInfo.tileSize
let cy = c.y * puzzleInfo.tileSize
2020-11-10 18:48:16 +01:00
return {
x: cx,
y: cy,
w: puzzleInfo.tileSize,
h: puzzleInfo.tileSize,
}
2020-11-07 11:35:29 +01:00
}
2020-11-08 12:58:21 +01:00
const pointSub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y })
2020-11-07 11:35:29 +01:00
2020-11-08 12:56:54 +01:00
const pointAdd = (a, b) => ({x: a.x + b.x, y: a.y + b.y})
2020-11-07 11:35:29 +01:00
// Returns the index of the puzzle tile with the highest z index
// that is not finished yet and that matches the position
const unfinishedTileByPos = (puzzle, pos) => {
2020-11-08 12:56:54 +01:00
let maxZ = -1
let tileIdx = -1
for (let idx = 0; idx < puzzle.tiles.length; idx++) {
const tile = puzzle.tiles[idx]
if (tile.owner === -1) {
continue
}
2020-11-07 11:35:29 +01:00
2020-11-10 18:48:16 +01:00
const collisionRect = {
x: tile.pos.x,
y: tile.pos.y,
w: puzzle.info.tileSize,
h: puzzle.info.tileSize,
}
2020-11-08 12:56:54 +01:00
if (pointInBounds(pos, collisionRect)) {
if (maxZ === -1 || tile.z > maxZ) {
maxZ = tile.z
tileIdx = idx
}
2020-11-07 11:35:29 +01:00
}
2020-11-08 12:56:54 +01:00
}
return tileIdx
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
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
}
}
2020-11-07 11:35:29 +01:00
async function loadPuzzleBitmaps(puzzle) {
// load bitmap, to determine the original size of the image
2020-11-09 00:29:07 +01:00
const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl)
2020-11-07 11:35:29 +01:00
// creation of tile bitmaps
// then create the final puzzle bitmap
// NOTE: this can decrease OR increase in size!
2020-11-10 18:48:16 +01:00
const bmpResized = await Graphics.resizeBitmap(bmp, puzzle.info.width, puzzle.info.height)
2020-11-08 12:56:54 +01:00
return await createPuzzleTileBitmaps(bmpResized, puzzle.tiles, puzzle.info)
2020-11-07 11:35:29 +01:00
}
function uniqId() {
return Date.now().toString(36) + Math.random().toString(36).substring(2)
}
function initme() {
2020-11-08 01:56:36 +01:00
// return uniqId()
2020-11-07 11:35:29 +01:00
let ID = localStorage.getItem("ID")
if (!ID) {
ID = uniqId()
localStorage.setItem("ID", ID)
}
return ID
}
async function main () {
2020-11-09 00:29:07 +01:00
let gameId = GAME_ID
2020-11-07 11:35:29 +01:00
let me = initme()
2020-11-09 00:29:07 +01:00
let cursorGrab = await Graphics.loadImageToBitmap('/grab.png')
let cursorHand = await Graphics.loadImageToBitmap('/hand.png')
2020-11-07 17:49:42 +01:00
2020-11-09 01:54:05 +01:00
const game = await Communication.connect(gameId, me)
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
const bitmaps = await loadPuzzleBitmaps(game.puzzle)
const puzzle = game.puzzle
const players = game.players
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// information for next render cycle
let rectPlayer = new DirtyRect()
let rerenderPlayer = true
let rectTable = new DirtyRect()
let rerenderTable = true
let rerender = true
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
const changePlayer = (change) => {
for (let k of Object.keys(change)) {
players[me][k] = change[k]
2020-11-07 16:33:28 +01:00
}
2020-11-09 01:54:05 +01:00
Communication.addChange({type: 'change_player', player: players[me]})
}
const changeData = (change) => {
for (let k of Object.keys(change)) {
puzzle.data[k] = change[k]
2020-11-07 16:33:28 +01:00
}
2020-11-09 01:54:05 +01:00
Communication.addChange({type: 'change_data', data: puzzle.data})
}
const changeTile = (t, change) => {
for (let k of Object.keys(change)) {
t[k] = change[k]
2020-11-07 16:33:28 +01:00
}
2020-11-09 01:54:05 +01:00
Communication.addChange({type: 'change_tile', tile: t})
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// Create a dom and attach adapters to it so we can work with it
const canvas = addCanvasToDom(Graphics.createCanvas())
2020-11-10 18:48:16 +01:00
const ctx = canvas.getContext('2d')
2020-11-09 01:54:05 +01:00
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)
}
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
players[change.player.id] = change.player
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
rectPlayer.add(viewport.worldToViewport(players[change.player.id]), cursorGrab.width)
} break;
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
case 'change_tile': {
rectTable.add(puzzle.tiles[change.tile.idx].pos, puzzle.info.tileDrawSize)
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
puzzle.tiles[change.tile.idx] = change.tile
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
rectTable.add(puzzle.tiles[change.tile.idx].pos, puzzle.info.tileDrawSize)
} break;
case 'change_data': {
puzzle.data = change.data
} break;
2020-11-08 01:56:36 +01:00
}
2020-11-09 01:54:05 +01:00
}
})
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// 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
2020-11-10 18:48:16 +01:00
const puzzleTableColor = '#222'
const puzzleTable = await Graphics.createBitmap(
2020-11-09 01:54:05 +01:00
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
2020-11-10 18:48:16 +01:00
const boardColor = '#505050'
const board = await Graphics.createBitmap(
2020-11-09 01:54:05 +01:00
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)
2020-11-07 11:35:29 +01:00
}
2020-11-07 16:33:28 +01:00
}
2020-11-09 01:54:05 +01:00
} else {
grouped.push(tile)
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
return grouped
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// 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
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
changeTile(tile, { group: targetGroup })
changeTile(other, { group: targetGroup })
2020-11-07 16:33:28 +01:00
2020-11-09 01:54:05 +01:00
if (searchGroups.length > 0) {
for (let tmp of puzzle.tiles) {
if (searchGroups.includes(tmp.group)) {
changeTile(tmp, { group: targetGroup })
2020-11-07 11:35:29 +01:00
}
2020-11-07 16:33:28 +01:00
}
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// determine if two tiles are grouped together
const areGrouped = (t1, t2) => {
return t1.group && t1.group === t2.group
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// get the center position of a tile
const tileCenterPos = (tile) => {
2020-11-10 18:48:16 +01:00
return rectCenter(tileRectByTile(tile))
2020-11-09 01:54:05 +01:00
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// get the would-be visible bounding rect if a tile was
// in given position
const tileRectByPos = (pos) => {
2020-11-10 18:48:16 +01:00
return {
x: pos.x,
y: pos.y,
w: puzzle.info.tileSize,
h: puzzle.info.tileSize,
}
2020-11-09 01:54:05 +01:00
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// get the current visible bounding rect for a tile
const tileRectByTile = (tile) => {
return tileRectByPos(tile.pos)
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
const tilesSortedByZIndex = () => {
const sorted = puzzle.tiles.slice()
return sorted.sort((t1, t2) => t1.z - t2.z)
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
const setGroupedZIndex = (tile, zIndex) => {
for (let t of getGroupedTiles(tile)) {
changeTile(t, { z: zIndex })
2020-11-07 16:33:28 +01:00
}
2020-11-09 01:54:05 +01:00
}
2020-11-07 16:33:28 +01:00
2020-11-09 01:54:05 +01:00
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 })
2020-11-07 16:33:28 +01:00
}
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
}
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
const moveGroupedTilesDiff = (tile, diffX, diffY) => {
for (let t of getGroupedTiles(tile)) {
changeTile(t, { pos: pointAdd(t.pos, { x: diffX, y: diffY }) })
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
// TODO: instead there could be a function to
// get min/max x/y of a group
rectTable.add(tileCenterPos(t), puzzle.info.tileDrawSize)
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
}
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 })
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
}
// ---------------------------------------------------------------
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
let _last_mouse = null
let _last_mouse_down = null
const onUpdate = () => {
let last_x = null
let last_y = null
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
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)
2020-11-08 20:29:09 +01:00
2020-11-09 01:54:05 +01:00
if (_last_mouse_down !== null) {
2020-11-08 01:56:36 +01:00
_last_mouse_down = mouse
2020-11-09 01:54:05 +01:00
2020-11-08 01:56:36 +01:00
if (last_x === null || last_y === null) {
last_x = mouse.x
last_y = mouse.y
2020-11-07 11:35:29 +01:00
}
2020-11-08 01:56:36 +01:00
if (grabbingTileIdx >= 0) {
2020-11-09 01:54:05 +01:00
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
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
2020-11-08 20:29:09 +01:00
}
2020-11-09 01:54:05 +01:00
}
} 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
}
2020-11-08 20:29:09 +01:00
2020-11-09 01:54:05 +01:00
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)
}
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
} 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)
2020-11-10 18:48:16 +01:00
if (rectCenterDistance(srcRect, dst) < puzzle.info.snapDistance) {
2020-11-09 01:54:05 +01:00
// Snap the tile to the final destination
moveGroupedTiles(tile, {
2020-11-10 18:48:16 +01:00
x: srcRect.x + boardPos.x,
y: srcRect.y + boardPos.y,
2020-11-09 01:54:05 +01:00
})
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)) {
2020-11-08 20:29:09 +01:00
return false
2020-11-08 01:56:36 +01:00
}
2020-11-10 18:48:16 +01:00
const trec_ = tileRectByTile(t)
const otrec = rectMoved(
tileRectByTile(other),
2020-11-09 01:54:05 +01:00
off[0] * puzzle.info.tileSize,
off[1] * puzzle.info.tileSize
)
2020-11-10 18:48:16 +01:00
if (rectCenterDistance(trec_, otrec) < puzzle.info.snapDistance) {
moveGroupedTiles(t, { x: otrec.x, y: otrec.y })
2020-11-09 01:54:05 +01:00
groupTiles(t, other)
setGroupedZIndex(t, t.z)
rectTable.add(tileCenterPos(t), puzzle.info.tileDrawSize)
return true
}
return false
}
2020-11-08 01:56:36 +01:00
2020-11-09 01:54:05 +01:00
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
2020-11-08 01:56:36 +01:00
}
2020-11-07 11:35:29 +01:00
}
2020-11-08 01:56:36 +01:00
}
2020-11-09 01:54:05 +01:00
}
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)
2020-11-08 01:56:36 +01:00
}
2020-11-09 01:54:05 +01:00
rectPlayer.add(mouse, cursorGrab.width)
2020-11-07 11:35:29 +01:00
}
2020-11-08 01:56:36 +01:00
}
2020-11-09 01:54:05 +01:00
// console.log(mouse)
_last_mouse = mouse
}
if (rectTable.get()) {
rerenderTable = true
}
if (rectPlayer.get()) {
rerenderPlayer = true
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
Communication.sendChanges()
}
2020-11-08 12:56:54 +01:00
2020-11-09 01:54:05 +01:00
const onRender = () => {
if (!rerenderTable && !rerenderPlayer && !rerender) {
return
2020-11-08 12:56:54 +01:00
}
2020-11-09 01:54:05 +01:00
2020-11-10 18:48:16 +01:00
let pos
let dim
2020-11-08 12:56:54 +01:00
2020-11-10 18:48:16 +01:00
if (DEBUG) Debug.checkpoint_start(0)
2020-11-07 11:35:29 +01:00
2020-11-10 18:48:16 +01:00
ctx.fillStyle = puzzleTableColor
ctx.fillRect(0, 0, canvas.width, canvas.height)
if (DEBUG) Debug.checkpoint('clear done')
2020-11-07 11:35:29 +01:00
2020-11-10 18:48:16 +01:00
// DRAW BOARD
// ---------------------------------------------------------------
pos = viewport.worldToViewport(boardPos)
dim = viewport.worldDimToViewport({w: board.width, h: board.height})
ctx.drawImage(board,
0, 0, board.width, board.height,
pos.x, pos.y, dim.w, dim.h
)
if (DEBUG) Debug.checkpoint('board done')
// DRAW TILES
// ---------------------------------------------------------------
for (let tile of tilesSortedByZIndex()) {
let bmp = bitmaps[tile.idx]
pos = viewport.worldToViewport({
x: puzzle.info.tileDrawOffset + tile.pos.x,
y: puzzle.info.tileDrawOffset + tile.pos.y,
})
dim = viewport.worldDimToViewport({
w: puzzle.info.tileDrawSize,
h: puzzle.info.tileDrawSize,
})
ctx.drawImage(bmp,
0, 0, bmp.width, bmp.height,
pos.x, pos.y, dim.w, dim.h
2020-11-09 01:54:05 +01:00
)
}
2020-11-10 18:48:16 +01:00
if (DEBUG) Debug.checkpoint('tiles done')
// DRAW PLAYERS
// ---------------------------------------------------------------
for (let id of Object.keys(players)) {
const p = players[id]
const cursor = p.down ? cursorGrab : cursorHand
const pos = viewport.worldToViewport(p)
ctx.drawImage(cursor, pos.x, pos.y)
2020-11-09 01:54:05 +01:00
}
2020-11-10 18:48:16 +01:00
//
if (DEBUG) Debug.checkpoint('players done')
2020-11-07 17:49:42 +01:00
2020-11-08 12:56:54 +01:00
2020-11-10 18:48:16 +01:00
if (DEBUG) Debug.checkpoint('all done')
2020-11-07 11:35:29 +01:00
2020-11-09 01:54:05 +01:00
rerenderTable = false
rerenderPlayer = false
rerender = false
rectTable.reset()
rectPlayer.reset()
2020-11-07 11:35:29 +01:00
}
2020-11-09 01:54:05 +01:00
run({
update: onUpdate,
render: onRender,
})
2020-11-07 11:35:29 +01:00
}
main()