feature/authoritative-server #1
7 changed files with 308 additions and 217 deletions
51
common/Protocol.js
Normal file
51
common/Protocol.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
SERVER_CLIENT_MESSAGE_PROTOCOL
|
||||||
|
NOTE: clients always send game id and their id
|
||||||
|
when creating sockets (via socket.protocol), so
|
||||||
|
this doesn't need to be set in each message data
|
||||||
|
|
||||||
|
NOTE: first value in the array is always the type of event/message
|
||||||
|
when describing them below, the value each has is used
|
||||||
|
instead of writing EVENT_TYPE or something ismilar
|
||||||
|
|
||||||
|
|
||||||
|
EV_CLIENT_EVENT: event triggered by clients and sent to server
|
||||||
|
[
|
||||||
|
EV_CLIENT_EVENT, // constant value, type of event
|
||||||
|
CLIENT_SEQ, // sequence number sent by client.
|
||||||
|
EV_DATA, // (eg. mouse input info)
|
||||||
|
]
|
||||||
|
|
||||||
|
EV_SERVER_EVENT: event sent to clients after recieving a client
|
||||||
|
event and processing it
|
||||||
|
[
|
||||||
|
EV_SERVER_EVENT, // constant value, type of event
|
||||||
|
CLIENT_ID, // user who sent the client event
|
||||||
|
CLIENT_SEQ, // sequence number of the client event msg
|
||||||
|
CHANGES_TRIGGERED_BY_CLIENT_EVENT,
|
||||||
|
]
|
||||||
|
|
||||||
|
EV_CLIENT_INIT: event sent by client to enter a game
|
||||||
|
[
|
||||||
|
EV_CLIENT_INIT, // constant value, type of event
|
||||||
|
]
|
||||||
|
|
||||||
|
EV_SERVER_INIT: event sent to one client after that client
|
||||||
|
connects to a game
|
||||||
|
[
|
||||||
|
EV_SERVER_INIT, // constant value, type of event
|
||||||
|
GAME, // complete game instance required by
|
||||||
|
// client to build client side of the game
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
const EV_SERVER_EVENT = 1
|
||||||
|
const EV_SERVER_INIT = 4
|
||||||
|
const EV_CLIENT_EVENT = 2
|
||||||
|
const EV_CLIENT_INIT = 3
|
||||||
|
|
||||||
|
export default {
|
||||||
|
EV_SERVER_EVENT,
|
||||||
|
EV_SERVER_INIT,
|
||||||
|
EV_CLIENT_EVENT,
|
||||||
|
EV_CLIENT_INIT,
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ export default class Camera {
|
||||||
}
|
}
|
||||||
|
|
||||||
// centered zoom
|
// centered zoom
|
||||||
|
// TODO: mouse-centered-zoom
|
||||||
this.x -= Math.round(((this.width / this.zoom) - (this.width / zoom)) / 2)
|
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.y -= Math.round(((this.height / this.zoom) - (this.height / zoom)) / 2)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,50 @@
|
||||||
import WsClient from './WsClient.js'
|
import WsClient from './WsClient.js'
|
||||||
|
import Protocol from './../common/Protocol.js'
|
||||||
|
|
||||||
const EV_SERVER_STATE_CHANGED = 1
|
/** @type WsClient */
|
||||||
const EV_SERVER_INIT = 4
|
|
||||||
const EV_CLIENT_MOUSE = 2
|
|
||||||
const EV_CLIENT_INIT = 3
|
|
||||||
|
|
||||||
let conn
|
let conn
|
||||||
let changesCallback = () => {}
|
let changesCallback = () => {}
|
||||||
|
|
||||||
function onChanges(callback) {
|
function onServerChange(callback) {
|
||||||
changesCallback = callback
|
changesCallback = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function send(message) {
|
||||||
|
conn.send(JSON.stringify(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
let clientSeq
|
||||||
|
let events
|
||||||
function connect(gameId, playerId) {
|
function connect(gameId, playerId) {
|
||||||
|
clientSeq = 0
|
||||||
|
events = {}
|
||||||
conn = new WsClient(WS_ADDRESS, playerId + '|' + gameId)
|
conn = new WsClient(WS_ADDRESS, playerId + '|' + gameId)
|
||||||
return new Promise(r => {
|
return new Promise(r => {
|
||||||
conn.connect()
|
conn.connect()
|
||||||
conn.send(JSON.stringify([EV_CLIENT_INIT]))
|
send([Protocol.EV_CLIENT_INIT])
|
||||||
conn.onSocket('message', async ({ data }) => {
|
conn.onSocket('message', async ({ data }) => {
|
||||||
const [type, typeData] = JSON.parse(data)
|
const msg = JSON.parse(data)
|
||||||
if (type === EV_SERVER_INIT) {
|
const msgType = msg[0]
|
||||||
const game = typeData
|
if (msgType === Protocol.EV_SERVER_INIT) {
|
||||||
|
const game = msg[1]
|
||||||
r(game)
|
r(game)
|
||||||
} else if (type === EV_SERVER_STATE_CHANGED) {
|
} else if (msgType === Protocol.EV_SERVER_EVENT) {
|
||||||
const changes = typeData
|
changesCallback(msg)
|
||||||
changesCallback(changes)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMouse(mouse) {
|
function sendClientEvent(mouse) {
|
||||||
conn.send(JSON.stringify([EV_CLIENT_MOUSE, mouse]))
|
// when sending event, increase number of sent events
|
||||||
|
// and add the event locally
|
||||||
|
clientSeq++;
|
||||||
|
events[clientSeq] = mouse
|
||||||
|
send([Protocol.EV_CLIENT_EVENT, clientSeq, events[clientSeq]])
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
connect,
|
connect,
|
||||||
onChanges,
|
onServerChange,
|
||||||
addMouse,
|
sendClientEvent,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
export default class EventAdapter {
|
|
||||||
constructor(canvas) {
|
|
||||||
this._mouseEvts = []
|
|
||||||
canvas.addEventListener('mousedown', this._mouseDown.bind(this))
|
|
||||||
canvas.addEventListener('mouseup', this._mouseUp.bind(this))
|
|
||||||
canvas.addEventListener('mousemove', this._mouseMove.bind(this))
|
|
||||||
canvas.addEventListener('wheel', this._wheel.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
consumeAll() {
|
|
||||||
if (this._mouseEvts.length === 0) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
const all = this._mouseEvts.slice()
|
|
||||||
this._mouseEvts = []
|
|
||||||
return all
|
|
||||||
}
|
|
||||||
|
|
||||||
_mouseDown(e) {
|
|
||||||
if (e.button === 0) {
|
|
||||||
this._mouseEvts.push({type: 'down', x: e.offsetX, y: e.offsetY})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_mouseUp(e) {
|
|
||||||
if (e.button === 0) {
|
|
||||||
this._mouseEvts.push({type: 'up', x: e.offsetX, y: e.offsetY})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_mouseMove(e) {
|
|
||||||
this._mouseEvts.push({type: 'move', x: e.offsetX, y: e.offsetY})
|
|
||||||
}
|
|
||||||
|
|
||||||
_wheel(e) {
|
|
||||||
this._mouseEvts.push({type: 'wheel', deltaY: e.deltaY, x: e.offsetX, y: e.offsetY})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
119
game/PuzzleGraphics.js
Normal file
119
game/PuzzleGraphics.js
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
import Geometry from '../common/Geometry.js'
|
||||||
|
import Graphics from './Graphics.js'
|
||||||
|
|
||||||
|
async function createPuzzleTileBitmaps(img, tiles, info) {
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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 = Geometry.pointAdd(topLeftEdge, { x: tileSize, y: 0 })
|
||||||
|
for (let i = 0; i < curvyCoords.length / 6; i++) {
|
||||||
|
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 = Geometry.pointAdd(topRightEdge, { x: 0, y: tileSize })
|
||||||
|
for (let i = 0; i < curvyCoords.length / 6; i++) {
|
||||||
|
let p1 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio })
|
||||||
|
let p2 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio })
|
||||||
|
let p3 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 5] * tileRatio })
|
||||||
|
path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
||||||
|
}
|
||||||
|
const bottomLeftEdge = Geometry.pointSub(bottomRightEdge, { x: tileSize, y: 0 })
|
||||||
|
for (let i = 0; i < curvyCoords.length / 6; i++) {
|
||||||
|
let p1 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio })
|
||||||
|
let p2 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio })
|
||||||
|
let p3 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio })
|
||||||
|
path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
||||||
|
}
|
||||||
|
paths[key] = path
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let tile of tiles) {
|
||||||
|
const srcRect = srcRectByIdx(info, tile.idx)
|
||||||
|
const path = pathForShape(info.shapes[tile.idx])
|
||||||
|
|
||||||
|
const c = Graphics.createCanvas(tileDrawSize, tileDrawSize)
|
||||||
|
const ctx = c.getContext('2d')
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
ctx.lineWidth = 2
|
||||||
|
ctx.stroke(path)
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
// -----------------------------------------------------------
|
||||||
|
ctx.save();
|
||||||
|
ctx.clip(path)
|
||||||
|
ctx.drawImage(
|
||||||
|
img,
|
||||||
|
srcRect.x - tileMarginWidth,
|
||||||
|
srcRect.y - tileMarginWidth,
|
||||||
|
tileDrawSize,
|
||||||
|
tileDrawSize,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
tileDrawSize,
|
||||||
|
tileDrawSize,
|
||||||
|
)
|
||||||
|
ctx.stroke(path)
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
bitmaps[tile.idx] = await createImageBitmap(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmaps
|
||||||
|
}
|
||||||
|
|
||||||
|
function srcRectByIdx(puzzleInfo, idx) {
|
||||||
|
const c = puzzleInfo.coords[idx]
|
||||||
|
return {
|
||||||
|
x: c.x * puzzleInfo.tileSize,
|
||||||
|
y: c.y * puzzleInfo.tileSize,
|
||||||
|
w: puzzleInfo.tileSize,
|
||||||
|
h: puzzleInfo.tileSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPuzzleBitmaps(puzzle) {
|
||||||
|
// load bitmap, to determine the original size of the image
|
||||||
|
const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl)
|
||||||
|
|
||||||
|
// creation of tile bitmaps
|
||||||
|
// then create the final puzzle bitmap
|
||||||
|
// NOTE: this can decrease OR increase in size!
|
||||||
|
const bmpResized = await Graphics.resizeBitmap(bmp, puzzle.info.width, puzzle.info.height)
|
||||||
|
return await createPuzzleTileBitmaps(bmpResized, puzzle.tiles, puzzle.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
loadPuzzleBitmaps,
|
||||||
|
}
|
||||||
233
game/index.js
233
game/index.js
|
|
@ -1,12 +1,11 @@
|
||||||
"use strict"
|
"use strict"
|
||||||
import {run} from './gameloop.js'
|
import {run} from './gameloop.js'
|
||||||
import Camera from './Camera.js'
|
import Camera from './Camera.js'
|
||||||
import EventAdapter from './EventAdapter.js'
|
|
||||||
import Graphics from './Graphics.js'
|
import Graphics from './Graphics.js'
|
||||||
import Debug from './Debug.js'
|
import Debug from './Debug.js'
|
||||||
import Communication from './Communication.js'
|
import Communication from './Communication.js'
|
||||||
import Geometry from './../common/Geometry.js'
|
|
||||||
import Util from './../common/Util.js'
|
import Util from './../common/Util.js'
|
||||||
|
import PuzzleGraphics from './PuzzleGraphics.js'
|
||||||
|
|
||||||
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
||||||
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]'
|
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]'
|
||||||
|
|
@ -18,121 +17,6 @@ function addCanvasToDom(canvas) {
|
||||||
return canvas
|
return canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createPuzzleTileBitmaps(img, tiles, info) {
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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 = Geometry.pointAdd(topLeftEdge, { x: tileSize, y: 0 })
|
|
||||||
for (let i = 0; i < curvyCoords.length / 6; i++) {
|
|
||||||
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 = Geometry.pointAdd(topRightEdge, { x: 0, y: tileSize })
|
|
||||||
for (let i = 0; i < curvyCoords.length / 6; i++) {
|
|
||||||
let p1 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio })
|
|
||||||
let p2 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio })
|
|
||||||
let p3 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 4] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 5] * tileRatio })
|
|
||||||
path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
|
||||||
}
|
|
||||||
const bottomLeftEdge = Geometry.pointSub(bottomRightEdge, { x: tileSize, y: 0 })
|
|
||||||
for (let i = 0; i < curvyCoords.length / 6; i++) {
|
|
||||||
let p1 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio })
|
|
||||||
let p2 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio })
|
|
||||||
let p3 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 5] * tileRatio, y: curvyCoords[i * 6 + 4] * tileRatio })
|
|
||||||
path.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
|
||||||
}
|
|
||||||
paths[key] = path
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let tile of tiles) {
|
|
||||||
const srcRect = srcRectByIdx(info, tile.idx)
|
|
||||||
const path = pathForShape(info.shapes[tile.idx])
|
|
||||||
|
|
||||||
const c = Graphics.createCanvas(tileDrawSize, tileDrawSize)
|
|
||||||
const ctx = c.getContext('2d')
|
|
||||||
// -----------------------------------------------------------
|
|
||||||
// -----------------------------------------------------------
|
|
||||||
ctx.lineWidth = 2
|
|
||||||
ctx.stroke(path)
|
|
||||||
// -----------------------------------------------------------
|
|
||||||
// -----------------------------------------------------------
|
|
||||||
ctx.save();
|
|
||||||
ctx.clip(path)
|
|
||||||
ctx.drawImage(
|
|
||||||
img,
|
|
||||||
srcRect.x - tileMarginWidth,
|
|
||||||
srcRect.y - tileMarginWidth,
|
|
||||||
tileDrawSize,
|
|
||||||
tileDrawSize,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
tileDrawSize,
|
|
||||||
tileDrawSize,
|
|
||||||
)
|
|
||||||
ctx.stroke(path)
|
|
||||||
ctx.restore();
|
|
||||||
|
|
||||||
bitmaps[tile.idx] = await createImageBitmap(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bitmaps
|
|
||||||
}
|
|
||||||
|
|
||||||
function srcRectByIdx(puzzleInfo, idx) {
|
|
||||||
let c = puzzleInfo.coords[idx]
|
|
||||||
let cx = c.x * puzzleInfo.tileSize
|
|
||||||
let cy = c.y * puzzleInfo.tileSize
|
|
||||||
return {
|
|
||||||
x: cx,
|
|
||||||
y: cy,
|
|
||||||
w: puzzleInfo.tileSize,
|
|
||||||
h: puzzleInfo.tileSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadPuzzleBitmaps(puzzle) {
|
|
||||||
// load bitmap, to determine the original size of the image
|
|
||||||
const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl)
|
|
||||||
|
|
||||||
// creation of tile bitmaps
|
|
||||||
// then create the final puzzle bitmap
|
|
||||||
// NOTE: this can decrease OR increase in size!
|
|
||||||
const bmpResized = await Graphics.resizeBitmap(bmp, puzzle.info.width, puzzle.info.height)
|
|
||||||
return await createPuzzleTileBitmaps(bmpResized, puzzle.tiles, puzzle.info)
|
|
||||||
}
|
|
||||||
|
|
||||||
function initme() {
|
function initme() {
|
||||||
// return uniqId()
|
// return uniqId()
|
||||||
let ID = localStorage.getItem("ID")
|
let ID = localStorage.getItem("ID")
|
||||||
|
|
@ -152,6 +36,63 @@ const getFirstOwnedTile = (puzzle, userId) => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default class EventAdapter {
|
||||||
|
constructor(canvas, viewport) {
|
||||||
|
this._mouseEvts = []
|
||||||
|
this._viewport = viewport
|
||||||
|
canvas.addEventListener('mousedown', this._mouseDown.bind(this))
|
||||||
|
canvas.addEventListener('mouseup', this._mouseUp.bind(this))
|
||||||
|
canvas.addEventListener('mousemove', this._mouseMove.bind(this))
|
||||||
|
canvas.addEventListener('wheel', this._wheel.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeAll() {
|
||||||
|
if (this._mouseEvts.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const all = this._mouseEvts.slice()
|
||||||
|
this._mouseEvts = []
|
||||||
|
return all
|
||||||
|
}
|
||||||
|
|
||||||
|
_mouseDown(e) {
|
||||||
|
if (e.button === 0) {
|
||||||
|
const pos = this._viewport.viewportToWorld({
|
||||||
|
x: e.offsetX,
|
||||||
|
y: e.offsetY,
|
||||||
|
})
|
||||||
|
this._mouseEvts.push(['down', pos.x, pos.y])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mouseUp(e) {
|
||||||
|
if (e.button === 0) {
|
||||||
|
const pos = this._viewport.viewportToWorld({
|
||||||
|
x: e.offsetX,
|
||||||
|
y: e.offsetY,
|
||||||
|
})
|
||||||
|
this._mouseEvts.push(['up', pos.x, pos.y])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_mouseMove(e) {
|
||||||
|
const pos = this._viewport.viewportToWorld({
|
||||||
|
x: e.offsetX,
|
||||||
|
y: e.offsetY,
|
||||||
|
})
|
||||||
|
this._mouseEvts.push(['move', pos.x, pos.y])
|
||||||
|
}
|
||||||
|
|
||||||
|
_wheel(e) {
|
||||||
|
const pos = this._viewport.viewportToWorld({
|
||||||
|
x: e.offsetX,
|
||||||
|
y: e.offsetY,
|
||||||
|
})
|
||||||
|
const evt = e.deltaY < 0 ? 'zoomin' : 'zoomout'
|
||||||
|
this._mouseEvts.push([evt, pos.x, pos.y])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let gameId = GAME_ID
|
let gameId = GAME_ID
|
||||||
let me = initme()
|
let me = initme()
|
||||||
|
|
@ -161,7 +102,7 @@ async function main () {
|
||||||
|
|
||||||
const game = await Communication.connect(gameId, me)
|
const game = await Communication.connect(gameId, me)
|
||||||
|
|
||||||
const bitmaps = await loadPuzzleBitmaps(game.puzzle)
|
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(game.puzzle)
|
||||||
const puzzle = game.puzzle
|
const puzzle = game.puzzle
|
||||||
const players = game.players
|
const players = game.players
|
||||||
|
|
||||||
|
|
@ -176,7 +117,6 @@ async function main () {
|
||||||
// Create a dom and attach adapters to it so we can work with it
|
// Create a dom and attach adapters to it so we can work with it
|
||||||
const canvas = addCanvasToDom(Graphics.createCanvas())
|
const canvas = addCanvasToDom(Graphics.createCanvas())
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
const evts = new EventAdapter(canvas)
|
|
||||||
|
|
||||||
// initialize some view data
|
// initialize some view data
|
||||||
// this global data will change according to input events
|
// this global data will change according to input events
|
||||||
|
|
@ -187,21 +127,27 @@ async function main () {
|
||||||
-(puzzle.info.table.height - viewport.height) /2
|
-(puzzle.info.table.height - viewport.height) /2
|
||||||
)
|
)
|
||||||
|
|
||||||
Communication.onChanges((changes) => {
|
const evts = new EventAdapter(canvas, viewport)
|
||||||
for (let [type, typeData] of changes) {
|
|
||||||
switch (type) {
|
Communication.onServerChange((msg) => {
|
||||||
|
const msgType = msg[0]
|
||||||
|
const evClientId = msg[1]
|
||||||
|
const evClientSeq = msg[2]
|
||||||
|
const evChanges = msg[3]
|
||||||
|
for(let [changeType, changeData] of evChanges) {
|
||||||
|
switch (changeType) {
|
||||||
case 'player': {
|
case 'player': {
|
||||||
if (typeData.id !== me) {
|
if (changeData.id !== me) {
|
||||||
players[typeData.id] = typeData
|
players[changeData.id] = changeData
|
||||||
rerender = true
|
rerender = true
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case 'tile': {
|
case 'tile': {
|
||||||
puzzle.tiles[typeData.idx] = typeData
|
puzzle.tiles[changeData.idx] = changeData
|
||||||
rerender = true
|
rerender = true
|
||||||
} break;
|
} break;
|
||||||
case 'data': {
|
case 'data': {
|
||||||
puzzle.data = typeData
|
puzzle.data = changeData
|
||||||
rerender = true
|
rerender = true
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
|
|
@ -217,40 +163,41 @@ async function main () {
|
||||||
return sorted.sort((t1, t2) => t1.z - t2.z)
|
return sorted.sort((t1, t2) => t1.z - t2.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let _last_mouse_down = null
|
let _last_mouse_down = null
|
||||||
const onUpdate = () => {
|
const onUpdate = () => {
|
||||||
for (let mouse of evts.consumeAll()) {
|
for (let evt of evts.consumeAll()) {
|
||||||
const tp = viewport.viewportToWorld(mouse)
|
const type = evt[0]
|
||||||
|
const pos = {x: evt[1], y: evt[2]}
|
||||||
|
|
||||||
if (mouse.type === 'move') {
|
if (type === 'move') {
|
||||||
Communication.addMouse(['move', tp.x, tp.y])
|
|
||||||
rerender = true
|
rerender = true
|
||||||
changePlayer({ x: tp.x, y: tp.y })
|
changePlayer(pos)
|
||||||
|
|
||||||
if (_last_mouse_down && !getFirstOwnedTile(puzzle, me)) {
|
if (_last_mouse_down && !getFirstOwnedTile(puzzle, me)) {
|
||||||
// move the cam
|
// move the cam
|
||||||
|
const mouse = viewport.worldToViewport(pos)
|
||||||
const diffX = Math.round(mouse.x - _last_mouse_down.x)
|
const diffX = Math.round(mouse.x - _last_mouse_down.x)
|
||||||
const diffY = Math.round(mouse.y - _last_mouse_down.y)
|
const diffY = Math.round(mouse.y - _last_mouse_down.y)
|
||||||
viewport.move(diffX, diffY)
|
viewport.move(diffX, diffY)
|
||||||
|
|
||||||
_last_mouse_down = mouse
|
_last_mouse_down = mouse
|
||||||
}
|
}
|
||||||
} else if (mouse.type === 'down') {
|
} else if (type === 'down') {
|
||||||
Communication.addMouse(['down', tp.x, tp.y])
|
_last_mouse_down = viewport.worldToViewport(pos)
|
||||||
_last_mouse_down = mouse
|
} else if (type === 'up') {
|
||||||
} else if (mouse.type === 'up') {
|
|
||||||
Communication.addMouse(['up', tp.x, tp.y])
|
|
||||||
_last_mouse_down = null
|
_last_mouse_down = null
|
||||||
} else if (mouse.type === 'wheel') {
|
} else if (type === 'zoomin') {
|
||||||
if (
|
if (viewport.zoomIn()) {
|
||||||
mouse.deltaY < 0 && viewport.zoomIn()
|
|
||||||
|| mouse.deltaY > 0 && viewport.zoomOut()
|
|
||||||
) {
|
|
||||||
rerender = true
|
rerender = true
|
||||||
changePlayer({ x: tp.x, y: tp.y })
|
changePlayer(pos)
|
||||||
|
}
|
||||||
|
} else if (type === 'zoomout') {
|
||||||
|
if (viewport.zoomOut()) {
|
||||||
|
rerender = true
|
||||||
|
changePlayer(pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Communication.sendClientEvent(evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,10 @@ import WebSocketServer from './WebSocketServer.js'
|
||||||
|
|
||||||
import express from 'express'
|
import express from 'express'
|
||||||
import config from './config.js'
|
import config from './config.js'
|
||||||
|
import Protocol from './../common/Protocol.js'
|
||||||
import Util from './../common/Util.js'
|
import Util from './../common/Util.js'
|
||||||
import Game from './Game.js'
|
import Game from './Game.js'
|
||||||
|
|
||||||
const EV_SERVER_STATE_CHANGED = 1
|
|
||||||
const EV_SERVER_INIT = 4
|
|
||||||
const EV_CLIENT_MOUSE = 2
|
|
||||||
const EV_CLIENT_INIT = 3
|
|
||||||
|
|
||||||
// desired number of tiles
|
// desired number of tiles
|
||||||
// actual calculated number can be higher
|
// actual calculated number can be higher
|
||||||
const TARGET_TILES = 1000
|
const TARGET_TILES = 1000
|
||||||
|
|
@ -65,35 +61,41 @@ app.use('/', (req, res, next) => {
|
||||||
const wss = new WebSocketServer(config.ws);
|
const wss = new WebSocketServer(config.ws);
|
||||||
|
|
||||||
const notify = (data, sockets) => {
|
const notify = (data, sockets) => {
|
||||||
// TODO: throttle
|
// TODO: throttle?
|
||||||
for (let socket of sockets) {
|
for (let socket of sockets) {
|
||||||
wss.notifyOne(data, socket)
|
wss.notifyOne(data, socket)
|
||||||
}
|
}
|
||||||
// console.log('notify', data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wss.on('message', async ({socket, data}) => {
|
wss.on('message', async ({socket, data}) => {
|
||||||
try {
|
try {
|
||||||
const proto = socket.protocol.split('|')
|
const proto = socket.protocol.split('|')
|
||||||
const playerId = proto[0]
|
const clientId = proto[0]
|
||||||
const gameId = proto[1]
|
const gameId = proto[1]
|
||||||
const [type, typeData] = JSON.parse(data)
|
const msg = JSON.parse(data)
|
||||||
switch (type) {
|
const msgType = msg[0]
|
||||||
case EV_CLIENT_INIT: {
|
switch (msgType) {
|
||||||
|
case Protocol.EV_CLIENT_INIT: {
|
||||||
if (!Game.exists(gameId)) {
|
if (!Game.exists(gameId)) {
|
||||||
await Game.createGame(gameId, TARGET_TILES, Util.choice(IMAGES))
|
await Game.createGame(gameId, TARGET_TILES, Util.choice(IMAGES))
|
||||||
}
|
}
|
||||||
Game.addPlayer(gameId, playerId)
|
Game.addPlayer(gameId, clientId)
|
||||||
Game.addSocket(gameId, socket)
|
Game.addSocket(gameId, socket)
|
||||||
|
|
||||||
wss.notifyOne([EV_SERVER_INIT, Game.get(gameId)], socket)
|
notify(
|
||||||
|
[Protocol.EV_SERVER_INIT, Game.get(gameId)],
|
||||||
|
[socket]
|
||||||
|
)
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case EV_CLIENT_MOUSE: {
|
case Protocol.EV_CLIENT_EVENT: {
|
||||||
const changes = Game.handleInput(gameId, playerId, typeData)
|
const clientSeq = msg[1]
|
||||||
if (changes.length > 0) {
|
const clientEvtData = msg[2]
|
||||||
notify([EV_SERVER_STATE_CHANGED, changes], Game.getSockets(gameId))
|
const changes = Game.handleInput(gameId, clientId, clientEvtData)
|
||||||
}
|
notify(
|
||||||
|
[Protocol.EV_SERVER_EVENT, clientId, clientSeq, changes],
|
||||||
|
Game.getSockets(gameId)
|
||||||
|
)
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue