everything
This commit is contained in:
parent
1e9abebfd3
commit
d592cef494
9 changed files with 619 additions and 539 deletions
|
|
@ -28,30 +28,26 @@ export default class Camera {
|
|||
this.y += y / this.zoom
|
||||
}
|
||||
|
||||
zoomOut() {
|
||||
const newzoom = Math.max(this.zoom - this.zoomStep, this.minZoom)
|
||||
if (newzoom !== this.zoom) {
|
||||
// centered zoom
|
||||
this.x -= ((this.width / this.zoom) - (this.width / newzoom)) / 2
|
||||
this.y -= ((this.height / this.zoom) - (this.height / newzoom)) / 2
|
||||
|
||||
this.zoom = newzoom
|
||||
return true
|
||||
}
|
||||
setZoom(newzoom) {
|
||||
const zoom = Math.min(Math.max(newzoom, this.minZoom), this.maxZoom)
|
||||
if (zoom == this.zoom) {
|
||||
return false
|
||||
}
|
||||
|
||||
// centered zoom
|
||||
this.x -= Math.round(((this.width / this.zoom) - (this.width / zoom)) / 2)
|
||||
this.y -= Math.round(((this.height / this.zoom) - (this.height / zoom)) / 2)
|
||||
|
||||
this.zoom = zoom
|
||||
return true
|
||||
}
|
||||
|
||||
zoomOut() {
|
||||
return this.setZoom(this.zoom - this.zoomStep)
|
||||
}
|
||||
|
||||
zoomIn() {
|
||||
const newzoom = Math.min(this.zoom + this.zoomStep, this.maxZoom)
|
||||
if (newzoom !== this.zoom) {
|
||||
// centered zoom
|
||||
this.x -= ((this.width / this.zoom) - (this.width / newzoom)) / 2
|
||||
this.y -= ((this.height / this.zoom) - (this.height / newzoom)) / 2
|
||||
|
||||
this.zoom = newzoom
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return this.setZoom(this.zoom + this.zoomStep)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -61,8 +57,8 @@ export default class Camera {
|
|||
*/
|
||||
viewportToWorld(coord) {
|
||||
return {
|
||||
x: (coord.x / this.zoom) - this.x,
|
||||
y: (coord.y / this.zoom) - this.y,
|
||||
x: Math.round((coord.x / this.zoom) - this.x),
|
||||
y: Math.round((coord.y / this.zoom) - this.y),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,15 +69,15 @@ export default class Camera {
|
|||
*/
|
||||
worldToViewport(coord) {
|
||||
return {
|
||||
x: (coord.x + this.x) * this.zoom,
|
||||
y: (coord.y + this.y) * this.zoom,
|
||||
x: Math.round((coord.x + this.x) * this.zoom),
|
||||
y: Math.round((coord.y + this.y) * this.zoom),
|
||||
}
|
||||
}
|
||||
|
||||
worldDimToViewport(dim) {
|
||||
return {
|
||||
w: dim.w * this.zoom,
|
||||
h: dim.h * this.zoom,
|
||||
w: Math.round(dim.w * this.zoom),
|
||||
h: Math.round(dim.h * this.zoom),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import WsClient from './WsClient.js'
|
||||
|
||||
const EV_SERVER_STATE_CHANGED = 1
|
||||
const EV_SERVER_INIT = 4
|
||||
const EV_CLIENT_MOUSE = 2
|
||||
const EV_CLIENT_INIT = 3
|
||||
|
||||
let conn
|
||||
let changesCallback = () => {}
|
||||
|
||||
|
|
@ -11,39 +16,26 @@ function connect(gameId, playerId) {
|
|||
conn = new WsClient(WS_ADDRESS, playerId + '|' + gameId)
|
||||
return new Promise(r => {
|
||||
conn.connect()
|
||||
conn.send(JSON.stringify({ type: 'init' }))
|
||||
conn.send(JSON.stringify([EV_CLIENT_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 [type, typeData] = JSON.parse(data)
|
||||
if (type === EV_SERVER_INIT) {
|
||||
const game = typeData
|
||||
r(game)
|
||||
} else if (type === EV_SERVER_STATE_CHANGED) {
|
||||
const changes = typeData
|
||||
changesCallback(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
|
||||
}
|
||||
function addMouse(mouse) {
|
||||
conn.send(JSON.stringify([EV_CLIENT_MOUSE, mouse]))
|
||||
}
|
||||
|
||||
export default {
|
||||
connect,
|
||||
onChanges,
|
||||
addChange,
|
||||
sendChanges,
|
||||
addMouse,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// import Bitmap from './Bitmap.js'
|
||||
|
||||
function createCanvas(width = 0, height = 0) {
|
||||
const c = document.createElement('canvas')
|
||||
c.width = width === 0 ? window.innerWidth : width
|
||||
|
|
|
|||
521
game/index.js
521
game/index.js
|
|
@ -5,6 +5,7 @@ import EventAdapter from './EventAdapter.js'
|
|||
import Graphics from './Graphics.js'
|
||||
import Debug from './Debug.js'
|
||||
import Communication from './Communication.js'
|
||||
import Geometry from './../common/Geometry.js'
|
||||
|
||||
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
||||
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]'
|
||||
|
|
@ -16,55 +17,6 @@ function addCanvasToDom(canvas) {
|
|||
return canvas
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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 getSurroundingTilesByIdx(puzzle, idx) {
|
||||
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]
|
||||
]
|
||||
}
|
||||
|
||||
async function createPuzzleTileBitmaps(img, tiles, info) {
|
||||
var tileSize = info.tileSize
|
||||
var tileMarginWidth = info.tileMarginWidth
|
||||
|
|
@ -93,30 +45,30 @@ async function createPuzzleTileBitmaps(img, tiles, info) {
|
|||
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 })
|
||||
const p1 = Geometry.pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.top * curvyCoords[i * 6 + 1] * tileRatio })
|
||||
const p2 = Geometry.pointAdd(topLeftEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.top * curvyCoords[i * 6 + 3] * tileRatio })
|
||||
const p3 = Geometry.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 })
|
||||
const topRightEdge = Geometry.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 })
|
||||
const p1 = Geometry.pointAdd(topRightEdge, { x: -shape.right * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio })
|
||||
const p2 = Geometry.pointAdd(topRightEdge, { x: -shape.right * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio })
|
||||
const p3 = Geometry.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 })
|
||||
const bottomRightEdge = Geometry.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 })
|
||||
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 })
|
||||
path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
||||
}
|
||||
const bottomLeftEdge = pointSub(bottomRightEdge, { x: tileSize, y: 0 })
|
||||
const bottomLeftEdge = Geometry.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 })
|
||||
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 })
|
||||
path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
||||
}
|
||||
paths[key] = path
|
||||
|
|
@ -169,64 +121,6 @@ function srcRectByIdx(puzzleInfo, idx) {
|
|||
}
|
||||
}
|
||||
|
||||
const pointSub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y })
|
||||
|
||||
const pointAdd = (a, b) => ({x: a.x + b.x, y: a.y + b.y})
|
||||
|
||||
// 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) => {
|
||||
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
|
||||
}
|
||||
|
||||
const collisionRect = {
|
||||
x: tile.pos.x,
|
||||
y: tile.pos.y,
|
||||
w: puzzle.info.tileSize,
|
||||
h: puzzle.info.tileSize,
|
||||
}
|
||||
if (pointInBounds(pos, collisionRect)) {
|
||||
if (maxZ === -1 || tile.z > maxZ) {
|
||||
maxZ = tile.z
|
||||
tileIdx = idx
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
|
@ -252,6 +146,15 @@ function initme() {
|
|||
return ID
|
||||
}
|
||||
|
||||
const getFirstOwnedTile = (puzzle, userId) => {
|
||||
for (let t of puzzle.tiles) {
|
||||
if (t.owner === userId) {
|
||||
return t
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function main () {
|
||||
let gameId = GAME_ID
|
||||
let me = initme()
|
||||
|
|
@ -265,30 +168,12 @@ async function main () {
|
|||
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
|
||||
|
|
@ -299,311 +184,67 @@ async function main () {
|
|||
// initialize some view data
|
||||
// this global data will change according to input events
|
||||
const viewport = new Camera(canvas)
|
||||
// center viewport
|
||||
viewport.move(
|
||||
-(puzzle.info.table.width - viewport.width) /2,
|
||||
-(puzzle.info.table.height - viewport.height) /2
|
||||
)
|
||||
|
||||
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)
|
||||
for (let [type, typeData] of changes) {
|
||||
switch (type) {
|
||||
case 'player': {
|
||||
if (typeData.id !== me) {
|
||||
players[typeData.id] = typeData
|
||||
rerender = true
|
||||
}
|
||||
|
||||
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)
|
||||
case 'tile': {
|
||||
puzzle.tiles[typeData.idx] = typeData
|
||||
rerender = true
|
||||
} break;
|
||||
case 'change_data': {
|
||||
puzzle.data = change.data
|
||||
case 'data': {
|
||||
puzzle.data = typeData
|
||||
rerender = true
|
||||
} 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 = '#222'
|
||||
const puzzleTable = await Graphics.createBitmap(
|
||||
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 = '#505050'
|
||||
const board = await Graphics.createBitmap(
|
||||
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)
|
||||
}
|
||||
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 rectCenter(tileRectByTile(tile))
|
||||
}
|
||||
|
||||
// get the would-be visible bounding rect if a tile was
|
||||
// in given position
|
||||
const tileRectByPos = (pos) => {
|
||||
return {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
w: puzzle.info.tileSize,
|
||||
h: 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') {
|
||||
Communication.addMouse(['move', tp.x, tp.y])
|
||||
rerender = true
|
||||
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) {
|
||||
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 {
|
||||
if (_last_mouse_down && !getFirstOwnedTile(puzzle, me)) {
|
||||
// move the cam
|
||||
const diffX = Math.round(mouse.x - last_x)
|
||||
const diffY = Math.round(mouse.y - last_y)
|
||||
const diffX = Math.round(mouse.x - _last_mouse_down.x)
|
||||
const diffY = Math.round(mouse.y - _last_mouse_down.y)
|
||||
viewport.move(diffX, diffY)
|
||||
rerender = true
|
||||
}
|
||||
|
||||
_last_mouse_down = mouse
|
||||
}
|
||||
} else if (mouse.type === 'down') {
|
||||
changePlayer({ down: true })
|
||||
rectPlayer.add(mouse, cursorGrab.width)
|
||||
|
||||
Communication.addMouse(['down', tp.x, tp.y])
|
||||
_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)
|
||||
|
||||
Communication.addMouse(['up', tp.x, tp.y])
|
||||
_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 (rectCenterDistance(srcRect, dst) < puzzle.info.snapDistance) {
|
||||
// Snap the tile to the final destination
|
||||
moveGroupedTiles(tile, {
|
||||
x: srcRect.x + boardPos.x,
|
||||
y: srcRect.y + 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
|
||||
}
|
||||
const trec_ = tileRectByTile(t)
|
||||
const otrec = rectMoved(
|
||||
tileRectByTile(other),
|
||||
off[0] * puzzle.info.tileSize,
|
||||
off[1] * puzzle.info.tileSize
|
||||
)
|
||||
if (rectCenterDistance(trec_, otrec) < puzzle.info.snapDistance) {
|
||||
moveGroupedTiles(t, { x: otrec.x, y: otrec.y })
|
||||
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()
|
||||
|
|
@ -611,27 +252,13 @@ async function main () {
|
|||
) {
|
||||
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
|
||||
}
|
||||
|
||||
Communication.sendChanges()
|
||||
}
|
||||
|
||||
const onRender = () => {
|
||||
if (!rerenderTable && !rerenderPlayer && !rerender) {
|
||||
if (!rerender) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -640,24 +267,33 @@ async function main () {
|
|||
|
||||
if (DEBUG) Debug.checkpoint_start(0)
|
||||
|
||||
ctx.fillStyle = puzzleTableColor
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
// CLEAR CTX
|
||||
// ---------------------------------------------------------------
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
if (DEBUG) Debug.checkpoint('clear done')
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
|
||||
// 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
|
||||
)
|
||||
pos = viewport.worldToViewport({
|
||||
x: (puzzle.info.table.width - puzzle.info.width) / 2,
|
||||
y: (puzzle.info.table.height - puzzle.info.height) / 2
|
||||
})
|
||||
dim = viewport.worldDimToViewport({
|
||||
w: puzzle.info.width,
|
||||
h: puzzle.info.height,
|
||||
})
|
||||
ctx.fillStyle = boardColor
|
||||
ctx.fillRect(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]
|
||||
const bmp = bitmaps[tile.idx]
|
||||
pos = viewport.worldToViewport({
|
||||
x: puzzle.info.tileDrawOffset + tile.pos.x,
|
||||
y: puzzle.info.tileDrawOffset + tile.pos.y,
|
||||
|
|
@ -672,6 +308,8 @@ async function main () {
|
|||
)
|
||||
}
|
||||
if (DEBUG) Debug.checkpoint('tiles done')
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
|
||||
// DRAW PLAYERS
|
||||
// ---------------------------------------------------------------
|
||||
|
|
@ -679,19 +317,16 @@ async function main () {
|
|||
const p = players[id]
|
||||
const cursor = p.down ? cursorGrab : cursorHand
|
||||
const pos = viewport.worldToViewport(p)
|
||||
ctx.drawImage(cursor, pos.x, pos.y)
|
||||
ctx.drawImage(cursor,
|
||||
Math.round(pos.x - cursor.width/2),
|
||||
Math.round(pos.y - cursor.height/2)
|
||||
)
|
||||
}
|
||||
//
|
||||
if (DEBUG) Debug.checkpoint('players done')
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
|
||||
if (DEBUG) Debug.checkpoint('all done')
|
||||
|
||||
rerenderTable = false
|
||||
rerenderPlayer = false
|
||||
rerender = false
|
||||
rectTable.reset()
|
||||
rectPlayer.reset()
|
||||
}
|
||||
|
||||
run({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue