feature/authoritative-server #1
5 changed files with 448 additions and 413 deletions
412
common/GameCommon.js
Normal file
412
common/GameCommon.js
Normal file
|
|
@ -0,0 +1,412 @@
|
||||||
|
import Geometry from './Geometry.js'
|
||||||
|
|
||||||
|
const GAMES = {}
|
||||||
|
|
||||||
|
function exists(gameId) {
|
||||||
|
return (!!GAMES[gameId]) || false
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGame(gameId, game) {
|
||||||
|
GAMES[gameId] = game
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPlayer(gameId, playerId) {
|
||||||
|
GAMES[gameId].players[playerId] = {
|
||||||
|
id: playerId,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
down: false
|
||||||
|
}
|
||||||
|
GAMES[gameId].evtInfos[playerId] = {
|
||||||
|
_last_mouse: null,
|
||||||
|
_last_mouse_down: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSocket(gameId, socket) {
|
||||||
|
const sockets = GAMES[gameId].sockets
|
||||||
|
|
||||||
|
if (!sockets.includes(socket)) {
|
||||||
|
sockets.push(socket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(gameId) {
|
||||||
|
return GAMES[gameId]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSockets(gameId) {
|
||||||
|
return GAMES[gameId].sockets
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePlayer(gameId, playerId, change) {
|
||||||
|
for (let k of Object.keys(change)) {
|
||||||
|
GAMES[gameId].players[playerId][k] = change[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeData(gameId, change) {
|
||||||
|
for (let k of Object.keys(change)) {
|
||||||
|
GAMES[gameId].puzzle.data[k] = change[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeTile(gameId, tileIdx, change) {
|
||||||
|
for (let k of Object.keys(change)) {
|
||||||
|
GAMES[gameId].puzzle.tiles[tileIdx][k] = change[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTile = (gameId, tileIdx) => {
|
||||||
|
return GAMES[gameId].puzzle.tiles[tileIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTileGroup = (gameId, tileIdx) => {
|
||||||
|
const tile = getTile(gameId, tileIdx)
|
||||||
|
return tile.group
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFinalTilePos = (gameId, tileIdx) => {
|
||||||
|
const info = GAMES[gameId].puzzle.info
|
||||||
|
const boardPos = {
|
||||||
|
x: (info.table.width - info.width) / 2,
|
||||||
|
y: (info.table.height - info.height) / 2
|
||||||
|
}
|
||||||
|
const srcPos = srcPosByTileIdx(gameId, tileIdx)
|
||||||
|
return Geometry.pointAdd(boardPos, srcPos)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTilePos = (gameId, tileIdx) => {
|
||||||
|
const tile = getTile(gameId, tileIdx)
|
||||||
|
return tile.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTileZIndex = (gameId, tileIdx) => {
|
||||||
|
const tile = getTile(gameId, tileIdx)
|
||||||
|
return tile.z
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFirstOwnedTileIdx = (gameId, userId) => {
|
||||||
|
for (let t of GAMES[gameId].puzzle.tiles) {
|
||||||
|
if (t.owner === userId) {
|
||||||
|
return t.idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxGroup = (gameId) => {
|
||||||
|
return GAMES[gameId].puzzle.data.maxGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxZIndex = (gameId) => {
|
||||||
|
return GAMES[gameId].puzzle.data.maxZ
|
||||||
|
}
|
||||||
|
|
||||||
|
const getMaxZIndexByTileIdxs = (gameId, tileIdxs) => {
|
||||||
|
let maxZ = 0
|
||||||
|
for (let tileIdx of tileIdxs) {
|
||||||
|
let tileZIndex = getTileZIndex(gameId, tileIdx)
|
||||||
|
if (tileZIndex > maxZ) {
|
||||||
|
maxZ = tileZIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxZ
|
||||||
|
}
|
||||||
|
|
||||||
|
function srcPosByTileIdx(gameId, tileIdx) {
|
||||||
|
const info = GAMES[gameId].puzzle.info
|
||||||
|
|
||||||
|
const c = info.coords[tileIdx]
|
||||||
|
const cx = c.x * info.tileSize
|
||||||
|
const cy = c.y * info.tileSize
|
||||||
|
|
||||||
|
return { x: cx, y: cy }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSurroundingTilesByIdx(gameId, tileIdx) {
|
||||||
|
const info = GAMES[gameId].puzzle.info
|
||||||
|
|
||||||
|
const _X = info.coords[tileIdx].x
|
||||||
|
const _Y = info.coords[tileIdx].y
|
||||||
|
|
||||||
|
return [
|
||||||
|
// top
|
||||||
|
(_Y > 0) ? (tileIdx - info.tilesX) : -1,
|
||||||
|
// right
|
||||||
|
(_X < info.tilesX - 1) ? (tileIdx + 1) : -1,
|
||||||
|
// bottom
|
||||||
|
(_Y < info.tilesY - 1) ? (tileIdx + info.tilesX) : -1,
|
||||||
|
// left
|
||||||
|
(_X > 0) ? (tileIdx - 1) : -1,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTilesZIndex = (gameId, tileIdxs, zIndex) => {
|
||||||
|
for (let tilesIdx of tileIdxs) {
|
||||||
|
changeTile(gameId, tilesIdx, { z: zIndex })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveTileDiff = (gameId, tileIdx, diff) => {
|
||||||
|
const oldPos = getTilePos(gameId, tileIdx)
|
||||||
|
const pos = Geometry.pointAdd(oldPos, diff)
|
||||||
|
changeTile(gameId, tileIdx, { pos })
|
||||||
|
}
|
||||||
|
|
||||||
|
const moveTilesDiff = (gameId, tileIdxs, diff) => {
|
||||||
|
for (let tileIdx of tileIdxs) {
|
||||||
|
moveTileDiff(gameId, tileIdx, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finishTiles = (gameId, tileIdxs) => {
|
||||||
|
for (let tileIdx of tileIdxs) {
|
||||||
|
changeTile(gameId, tileIdx, { owner: -1, z: 1 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setTilesOwner = (gameId, tileIdxs, owner) => {
|
||||||
|
for (let tileIdx of tileIdxs) {
|
||||||
|
changeTile(gameId, tileIdx, { owner })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all grouped tiles for a tile
|
||||||
|
function getGroupedTileIdxs(gameId, tileIdx) {
|
||||||
|
const tiles = GAMES[gameId].puzzle.tiles
|
||||||
|
const tile = tiles[tileIdx]
|
||||||
|
|
||||||
|
const grouped = []
|
||||||
|
if (tile.group) {
|
||||||
|
for (let other of tiles) {
|
||||||
|
if (other.group === tile.group) {
|
||||||
|
grouped.push(other.idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
grouped.push(tile.idx)
|
||||||
|
}
|
||||||
|
return grouped
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the index of the puzzle tile with the highest z index
|
||||||
|
// that is not finished yet and that matches the position
|
||||||
|
const freeTileIdxByPos = (gameId, pos) => {
|
||||||
|
let info = GAMES[gameId].puzzle.info
|
||||||
|
let tiles = GAMES[gameId].puzzle.tiles
|
||||||
|
|
||||||
|
let maxZ = -1
|
||||||
|
let tileIdx = -1
|
||||||
|
for (let idx = 0; idx < tiles.length; idx++) {
|
||||||
|
const tile = tiles[idx]
|
||||||
|
if (tile.owner !== 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const collisionRect = {
|
||||||
|
x: tile.pos.x,
|
||||||
|
y: tile.pos.y,
|
||||||
|
w: info.tileSize,
|
||||||
|
h: info.tileSize,
|
||||||
|
}
|
||||||
|
if (Geometry.pointInBounds(pos, collisionRect)) {
|
||||||
|
if (maxZ === -1 || tile.z > maxZ) {
|
||||||
|
maxZ = tile.z
|
||||||
|
tileIdx = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tileIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine if two tiles are grouped together
|
||||||
|
const areGrouped = (gameId, tileIdx1, tileIdx2) => {
|
||||||
|
const g1 = getTileGroup(gameId, tileIdx1)
|
||||||
|
const g2 = getTileGroup(gameId, tileIdx2)
|
||||||
|
return g1 && g1 === g2
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleInput(gameId, playerId, input) {
|
||||||
|
let puzzle = GAMES[gameId].puzzle
|
||||||
|
let players = GAMES[gameId].players
|
||||||
|
let evtInfo = GAMES[gameId].evtInfos[playerId]
|
||||||
|
|
||||||
|
let changes = []
|
||||||
|
|
||||||
|
const _dataChange = () => {
|
||||||
|
changes.push(['data', puzzle.data])
|
||||||
|
}
|
||||||
|
|
||||||
|
const _tileChange = (tileIdx) => {
|
||||||
|
changes.push(['tile', getTile(gameId, tileIdx)])
|
||||||
|
}
|
||||||
|
|
||||||
|
const _tileChanges = (tileIdxs) => {
|
||||||
|
for (let tileIdx of tileIdxs) {
|
||||||
|
_tileChange(tileIdx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _playerChange = () => {
|
||||||
|
changes.push(['player', players[playerId]])
|
||||||
|
}
|
||||||
|
|
||||||
|
// put both tiles (and their grouped tiles) in the same group
|
||||||
|
const groupTiles = (gameId, tileIdx1, tileIdx2) => {
|
||||||
|
let tiles = GAMES[gameId].puzzle.tiles
|
||||||
|
let group1 = getTileGroup(gameId, tileIdx1)
|
||||||
|
let group2 = getTileGroup(gameId, tileIdx2)
|
||||||
|
|
||||||
|
let group
|
||||||
|
let searchGroups = []
|
||||||
|
if (group1) {
|
||||||
|
searchGroups.push(group1)
|
||||||
|
}
|
||||||
|
if (group2) {
|
||||||
|
searchGroups.push(group2)
|
||||||
|
}
|
||||||
|
if (group1) {
|
||||||
|
group = group1
|
||||||
|
} else if (group2) {
|
||||||
|
group = group2
|
||||||
|
} else {
|
||||||
|
let maxGroup = getMaxGroup(gameId) + 1
|
||||||
|
changeData(gameId, { maxGroup })
|
||||||
|
_dataChange()
|
||||||
|
group = getMaxGroup(gameId)
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTile(gameId, tileIdx1, { group })
|
||||||
|
_tileChange(tileIdx1)
|
||||||
|
changeTile(gameId, tileIdx2, { group })
|
||||||
|
_tileChange(tileIdx2)
|
||||||
|
|
||||||
|
// TODO: strange
|
||||||
|
if (searchGroups.length > 0) {
|
||||||
|
for (let tile of tiles) {
|
||||||
|
if (searchGroups.includes(tile.group)) {
|
||||||
|
changeTile(gameId, tile.idx, { group })
|
||||||
|
_tileChange(tile.idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let [type, x, y] = input
|
||||||
|
let pos = {x, y}
|
||||||
|
if (type === 'down') {
|
||||||
|
changePlayer(gameId, playerId, { down: true })
|
||||||
|
_playerChange()
|
||||||
|
evtInfo._last_mouse_down = pos
|
||||||
|
|
||||||
|
const tileIdxAtPos = freeTileIdxByPos(gameId, pos)
|
||||||
|
if (tileIdxAtPos >= 0) {
|
||||||
|
console.log('tile: ', tileIdxAtPos)
|
||||||
|
let maxZ = getMaxZIndex(gameId) + 1
|
||||||
|
changeData(gameId, { maxZ })
|
||||||
|
_dataChange()
|
||||||
|
const tileIdxs = getGroupedTileIdxs(gameId, tileIdxAtPos)
|
||||||
|
setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId))
|
||||||
|
setTilesOwner(gameId, tileIdxs, playerId)
|
||||||
|
_tileChanges(tileIdxs)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (type === 'move') {
|
||||||
|
changePlayer(gameId, playerId, pos)
|
||||||
|
_playerChange()
|
||||||
|
|
||||||
|
if (evtInfo._last_mouse_down !== null) {
|
||||||
|
let tileIdx = getFirstOwnedTileIdx(gameId, playerId)
|
||||||
|
if (tileIdx >= 0) {
|
||||||
|
const diffX = x - evtInfo._last_mouse_down.x
|
||||||
|
const diffY = y - evtInfo._last_mouse_down.y
|
||||||
|
const diff = { x: diffX, y: diffY }
|
||||||
|
const tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
||||||
|
moveTilesDiff(gameId, tileIdxs, diff)
|
||||||
|
_tileChanges(tileIdxs)
|
||||||
|
}
|
||||||
|
|
||||||
|
evtInfo._last_mouse_down = pos
|
||||||
|
}
|
||||||
|
} else if (type === 'up') {
|
||||||
|
changePlayer(gameId, playerId, { down: false })
|
||||||
|
_playerChange()
|
||||||
|
evtInfo._last_mouse_down = null
|
||||||
|
|
||||||
|
let tileIdx = getFirstOwnedTileIdx(gameId, playerId)
|
||||||
|
if (tileIdx >= 0) {
|
||||||
|
// drop the tile(s)
|
||||||
|
let tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
||||||
|
setTilesOwner(gameId, tileIdxs, 0)
|
||||||
|
_tileChanges(tileIdxs)
|
||||||
|
|
||||||
|
// Check if the tile was dropped near the final location
|
||||||
|
let tilePos = getTilePos(gameId, tileIdx)
|
||||||
|
let finalPos = getFinalTilePos(gameId, tileIdx)
|
||||||
|
if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) {
|
||||||
|
let diff = Geometry.pointSub(finalPos, tilePos)
|
||||||
|
// Snap the tile to the final destination
|
||||||
|
moveTilesDiff(gameId, tileIdxs, diff)
|
||||||
|
finishTiles(gameId, tileIdxs)
|
||||||
|
_tileChanges(tileIdxs)
|
||||||
|
} else {
|
||||||
|
// Snap to other tiles
|
||||||
|
const check = (gameId, tileIdx, otherTileIdx, off) => {
|
||||||
|
let info = GAMES[gameId].puzzle.info
|
||||||
|
if (otherTileIdx < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (areGrouped(gameId, tileIdx, otherTileIdx)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const tilePos = getTilePos(gameId, tileIdx)
|
||||||
|
const dstPos = Geometry.pointAdd(
|
||||||
|
getTilePos(gameId, otherTileIdx),
|
||||||
|
{x: off[0] * info.tileSize, y: off[1] * info.tileSize}
|
||||||
|
)
|
||||||
|
if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) {
|
||||||
|
let diff = Geometry.pointSub(dstPos, tilePos)
|
||||||
|
let tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
||||||
|
moveTilesDiff(gameId, tileIdxs, diff)
|
||||||
|
groupTiles(gameId, tileIdx, otherTileIdx)
|
||||||
|
tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
||||||
|
const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs)
|
||||||
|
console.log('z:' , zIndex, tileIdxs)
|
||||||
|
setTilesZIndex(gameId, tileIdxs, zIndex)
|
||||||
|
_tileChanges(tileIdxs)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let tileIdxTmp of getGroupedTileIdxs(gameId, tileIdx)) {
|
||||||
|
let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp)
|
||||||
|
if (
|
||||||
|
check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top
|
||||||
|
|| check(gameId, tileIdxTmp, othersIdxs[1], [-1, 0]) // right
|
||||||
|
|| check(gameId, tileIdxTmp, othersIdxs[2], [0, -1]) // bottom
|
||||||
|
|| check(gameId, tileIdxTmp, othersIdxs[3], [1, 0]) // left
|
||||||
|
) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// console.log(mouse)
|
||||||
|
evtInfo._last_mouse = pos
|
||||||
|
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setGame,
|
||||||
|
exists,
|
||||||
|
addPlayer,
|
||||||
|
addSocket,
|
||||||
|
get,
|
||||||
|
getSockets,
|
||||||
|
handleInput,
|
||||||
|
}
|
||||||
|
|
@ -15,10 +15,10 @@ function send(message) {
|
||||||
|
|
||||||
let clientSeq
|
let clientSeq
|
||||||
let events
|
let events
|
||||||
function connect(gameId, playerId) {
|
function connect(gameId, clientId) {
|
||||||
clientSeq = 0
|
clientSeq = 0
|
||||||
events = {}
|
events = {}
|
||||||
conn = new WsClient(WS_ADDRESS, playerId + '|' + gameId)
|
conn = new WsClient(WS_ADDRESS, clientId + '|' + gameId)
|
||||||
return new Promise(r => {
|
return new Promise(r => {
|
||||||
conn.connect()
|
conn.connect()
|
||||||
send([Protocol.EV_CLIENT_INIT])
|
send([Protocol.EV_CLIENT_INIT])
|
||||||
|
|
@ -29,6 +29,11 @@ function connect(gameId, playerId) {
|
||||||
const game = msg[1]
|
const game = msg[1]
|
||||||
r(game)
|
r(game)
|
||||||
} else if (msgType === Protocol.EV_SERVER_EVENT) {
|
} else if (msgType === Protocol.EV_SERVER_EVENT) {
|
||||||
|
const msgClientId = msg[1]
|
||||||
|
const msgClientSeq = msg[2]
|
||||||
|
if (msgClientId === clientId && events[msgClientSeq]) {
|
||||||
|
delete events[msgClientSeq]
|
||||||
|
}
|
||||||
changesCallback(msg)
|
changesCallback(msg)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
6
game/Game.js
Normal file
6
game/Game.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import GameCommon from './../common/GameCommon.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
createGame: GameCommon.setGame,
|
||||||
|
handleInput: GameCommon.handleInput,
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import Debug from './Debug.js'
|
||||||
import Communication from './Communication.js'
|
import Communication from './Communication.js'
|
||||||
import Util from './../common/Util.js'
|
import Util from './../common/Util.js'
|
||||||
import PuzzleGraphics from './PuzzleGraphics.js'
|
import PuzzleGraphics from './PuzzleGraphics.js'
|
||||||
|
import Game from './Game.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 ]'
|
||||||
|
|
@ -95,12 +96,13 @@ export default class EventAdapter {
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
let gameId = GAME_ID
|
let gameId = GAME_ID
|
||||||
let me = initme()
|
let CLIENT_ID = initme()
|
||||||
|
|
||||||
let cursorGrab = await Graphics.loadImageToBitmap('/grab.png')
|
let cursorGrab = await Graphics.loadImageToBitmap('/grab.png')
|
||||||
let cursorHand = await Graphics.loadImageToBitmap('/hand.png')
|
let cursorHand = await Graphics.loadImageToBitmap('/hand.png')
|
||||||
|
|
||||||
const game = await Communication.connect(gameId, me)
|
const game = await Communication.connect(gameId, CLIENT_ID)
|
||||||
|
Game.createGame(GAME_ID, game);
|
||||||
|
|
||||||
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(game.puzzle)
|
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(game.puzzle)
|
||||||
const puzzle = game.puzzle
|
const puzzle = game.puzzle
|
||||||
|
|
@ -110,7 +112,7 @@ async function main() {
|
||||||
|
|
||||||
const changePlayer = (change) => {
|
const changePlayer = (change) => {
|
||||||
for (let k of Object.keys(change)) {
|
for (let k of Object.keys(change)) {
|
||||||
players[me][k] = change[k]
|
players[CLIENT_ID][k] = change[k]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +139,7 @@ async function main() {
|
||||||
for(let [changeType, changeData] of evChanges) {
|
for(let [changeType, changeData] of evChanges) {
|
||||||
switch (changeType) {
|
switch (changeType) {
|
||||||
case 'player': {
|
case 'player': {
|
||||||
if (changeData.id !== me) {
|
if (changeData.id !== CLIENT_ID) {
|
||||||
players[changeData.id] = changeData
|
players[changeData.id] = changeData
|
||||||
rerender = true
|
rerender = true
|
||||||
}
|
}
|
||||||
|
|
@ -166,14 +168,16 @@ async function main() {
|
||||||
let _last_mouse_down = null
|
let _last_mouse_down = null
|
||||||
const onUpdate = () => {
|
const onUpdate = () => {
|
||||||
for (let evt of evts.consumeAll()) {
|
for (let evt of evts.consumeAll()) {
|
||||||
|
|
||||||
|
// LOCAL ONLY CHANGES
|
||||||
|
// -------------------------------------------------------------
|
||||||
const type = evt[0]
|
const type = evt[0]
|
||||||
const pos = {x: evt[1], y: evt[2]}
|
const pos = {x: evt[1], y: evt[2]}
|
||||||
|
|
||||||
if (type === 'move') {
|
if (type === 'move') {
|
||||||
rerender = true
|
rerender = true
|
||||||
changePlayer(pos)
|
changePlayer(pos)
|
||||||
|
|
||||||
if (_last_mouse_down && !getFirstOwnedTile(puzzle, me)) {
|
if (_last_mouse_down && !getFirstOwnedTile(puzzle, CLIENT_ID)) {
|
||||||
// move the cam
|
// move the cam
|
||||||
const mouse = viewport.worldToViewport(pos)
|
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)
|
||||||
|
|
@ -197,6 +201,10 @@ async function main() {
|
||||||
changePlayer(pos)
|
changePlayer(pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LOCAL + SERVER CHANGES
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
Game.handleInput(GAME_ID, CLIENT_ID, evt)
|
||||||
Communication.sendClientEvent(evt)
|
Communication.sendClientEvent(evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
414
server/Game.js
414
server/Game.js
|
|
@ -1,419 +1,23 @@
|
||||||
import { createPuzzle } from './Puzzle.js'
|
import { createPuzzle } from './Puzzle.js'
|
||||||
import Geometry from './../common/Geometry.js'
|
import GameCommon from './../common/GameCommon.js'
|
||||||
|
|
||||||
const GAMES = {}
|
|
||||||
|
|
||||||
function exists(gameId) {
|
|
||||||
return (!!GAMES[gameId]) || false
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createGame(gameId, targetTiles, image) {
|
async function createGame(gameId, targetTiles, image) {
|
||||||
GAMES[gameId] = {
|
const game = {
|
||||||
puzzle: await createPuzzle(targetTiles, image),
|
puzzle: await createPuzzle(targetTiles, image),
|
||||||
players: {},
|
players: {},
|
||||||
|
|
||||||
sockets: [],
|
sockets: [],
|
||||||
evtInfos: {},
|
evtInfos: {},
|
||||||
}
|
}
|
||||||
|
GameCommon.setGame(gameId, game)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPlayer(gameId, playerId) {
|
|
||||||
GAMES[gameId].players[playerId] = {
|
|
||||||
id: playerId,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
down: false
|
|
||||||
}
|
|
||||||
GAMES[gameId].evtInfos[playerId] = {
|
|
||||||
_last_mouse: null,
|
|
||||||
_last_mouse_down: null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSocket(gameId, socket) {
|
|
||||||
const sockets = GAMES[gameId].sockets
|
|
||||||
|
|
||||||
if (!sockets.includes(socket)) {
|
|
||||||
sockets.push(socket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(gameId) {
|
|
||||||
return GAMES[gameId]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSockets(gameId) {
|
|
||||||
return GAMES[gameId].sockets
|
|
||||||
}
|
|
||||||
|
|
||||||
function changePlayer(gameId, playerId, change) {
|
|
||||||
for (let k of Object.keys(change)) {
|
|
||||||
GAMES[gameId].players[playerId][k] = change[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeData(gameId, change) {
|
|
||||||
for (let k of Object.keys(change)) {
|
|
||||||
GAMES[gameId].puzzle.data[k] = change[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeTile(gameId, tileIdx, change) {
|
|
||||||
for (let k of Object.keys(change)) {
|
|
||||||
GAMES[gameId].puzzle.tiles[tileIdx][k] = change[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTile = (gameId, tileIdx) => {
|
|
||||||
return GAMES[gameId].puzzle.tiles[tileIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTileGroup = (gameId, tileIdx) => {
|
|
||||||
const tile = getTile(gameId, tileIdx)
|
|
||||||
return tile.group
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFinalTilePos = (gameId, tileIdx) => {
|
|
||||||
const info = GAMES[gameId].puzzle.info
|
|
||||||
const boardPos = {
|
|
||||||
x: (info.table.width - info.width) / 2,
|
|
||||||
y: (info.table.height - info.height) / 2
|
|
||||||
}
|
|
||||||
const srcPos = srcPosByTileIdx(gameId, tileIdx)
|
|
||||||
return Geometry.pointAdd(boardPos, srcPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTilePos = (gameId, tileIdx) => {
|
|
||||||
const tile = getTile(gameId, tileIdx)
|
|
||||||
return tile.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTileZIndex = (gameId, tileIdx) => {
|
|
||||||
const tile = getTile(gameId, tileIdx)
|
|
||||||
return tile.z
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFirstOwnedTileIdx = (gameId, userId) => {
|
|
||||||
for (let t of GAMES[gameId].puzzle.tiles) {
|
|
||||||
if (t.owner === userId) {
|
|
||||||
return t.idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMaxGroup = (gameId) => {
|
|
||||||
return GAMES[gameId].puzzle.data.maxGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMaxZIndex = (gameId) => {
|
|
||||||
return GAMES[gameId].puzzle.data.maxZ
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMaxZIndexByTileIdxs = (gameId, tileIdxs) => {
|
|
||||||
let maxZ = 0
|
|
||||||
for (let tileIdx of tileIdxs) {
|
|
||||||
let tileZIndex = getTileZIndex(gameId, tileIdx)
|
|
||||||
if (tileZIndex > maxZ) {
|
|
||||||
maxZ = tileZIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxZ
|
|
||||||
}
|
|
||||||
|
|
||||||
function srcPosByTileIdx(gameId, tileIdx) {
|
|
||||||
const info = GAMES[gameId].puzzle.info
|
|
||||||
|
|
||||||
const c = info.coords[tileIdx]
|
|
||||||
const cx = c.x * info.tileSize
|
|
||||||
const cy = c.y * info.tileSize
|
|
||||||
|
|
||||||
return { x: cx, y: cy }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSurroundingTilesByIdx(gameId, tileIdx) {
|
|
||||||
const info = GAMES[gameId].puzzle.info
|
|
||||||
|
|
||||||
const _X = info.coords[tileIdx].x
|
|
||||||
const _Y = info.coords[tileIdx].y
|
|
||||||
|
|
||||||
return [
|
|
||||||
// top
|
|
||||||
(_Y > 0) ? (tileIdx - info.tilesX) : -1,
|
|
||||||
// right
|
|
||||||
(_X < info.tilesX - 1) ? (tileIdx + 1) : -1,
|
|
||||||
// bottom
|
|
||||||
(_Y < info.tilesY - 1) ? (tileIdx + info.tilesX) : -1,
|
|
||||||
// left
|
|
||||||
(_X > 0) ? (tileIdx - 1) : -1,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const setTilesZIndex = (gameId, tileIdxs, zIndex) => {
|
|
||||||
for (let tilesIdx of tileIdxs) {
|
|
||||||
changeTile(gameId, tilesIdx, { z: zIndex })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveTileDiff = (gameId, tileIdx, diff) => {
|
|
||||||
const oldPos = getTilePos(gameId, tileIdx)
|
|
||||||
const pos = Geometry.pointAdd(oldPos, diff)
|
|
||||||
changeTile(gameId, tileIdx, { pos })
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveTilesDiff = (gameId, tileIdxs, diff) => {
|
|
||||||
for (let tileIdx of tileIdxs) {
|
|
||||||
moveTileDiff(gameId, tileIdx, diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const finishTiles = (gameId, tileIdxs) => {
|
|
||||||
for (let tileIdx of tileIdxs) {
|
|
||||||
changeTile(gameId, tileIdx, { owner: -1, z: 1 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const setTilesOwner = (gameId, tileIdxs, owner) => {
|
|
||||||
for (let tileIdx of tileIdxs) {
|
|
||||||
changeTile(gameId, tileIdx, { owner })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all grouped tiles for a tile
|
|
||||||
function getGroupedTileIdxs(gameId, tileIdx) {
|
|
||||||
const tiles = GAMES[gameId].puzzle.tiles
|
|
||||||
const tile = tiles[tileIdx]
|
|
||||||
|
|
||||||
const grouped = []
|
|
||||||
if (tile.group) {
|
|
||||||
for (let other of tiles) {
|
|
||||||
if (other.group === tile.group) {
|
|
||||||
grouped.push(other.idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
grouped.push(tile.idx)
|
|
||||||
}
|
|
||||||
return grouped
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the index of the puzzle tile with the highest z index
|
|
||||||
// that is not finished yet and that matches the position
|
|
||||||
const freeTileIdxByPos = (gameId, pos) => {
|
|
||||||
let info = GAMES[gameId].puzzle.info
|
|
||||||
let tiles = GAMES[gameId].puzzle.tiles
|
|
||||||
|
|
||||||
let maxZ = -1
|
|
||||||
let tileIdx = -1
|
|
||||||
for (let idx = 0; idx < tiles.length; idx++) {
|
|
||||||
const tile = tiles[idx]
|
|
||||||
if (tile.owner !== 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const collisionRect = {
|
|
||||||
x: tile.pos.x,
|
|
||||||
y: tile.pos.y,
|
|
||||||
w: info.tileSize,
|
|
||||||
h: info.tileSize,
|
|
||||||
}
|
|
||||||
if (Geometry.pointInBounds(pos, collisionRect)) {
|
|
||||||
if (maxZ === -1 || tile.z > maxZ) {
|
|
||||||
maxZ = tile.z
|
|
||||||
tileIdx = idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tileIdx
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine if two tiles are grouped together
|
|
||||||
const areGrouped = (gameId, tileIdx1, tileIdx2) => {
|
|
||||||
const g1 = getTileGroup(gameId, tileIdx1)
|
|
||||||
const g2 = getTileGroup(gameId, tileIdx2)
|
|
||||||
return g1 && g1 === g2
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInput(gameId, playerId, input) {
|
|
||||||
let puzzle = GAMES[gameId].puzzle
|
|
||||||
let players = GAMES[gameId].players
|
|
||||||
let evtInfo = GAMES[gameId].evtInfos[playerId]
|
|
||||||
|
|
||||||
let changes = []
|
|
||||||
|
|
||||||
const _dataChange = () => {
|
|
||||||
changes.push(['data', puzzle.data])
|
|
||||||
}
|
|
||||||
|
|
||||||
const _tileChange = (tileIdx) => {
|
|
||||||
changes.push(['tile', getTile(gameId, tileIdx)])
|
|
||||||
}
|
|
||||||
|
|
||||||
const _tileChanges = (tileIdxs) => {
|
|
||||||
for (let tileIdx of tileIdxs) {
|
|
||||||
_tileChange(tileIdx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _playerChange = () => {
|
|
||||||
changes.push(['player', players[playerId]])
|
|
||||||
}
|
|
||||||
|
|
||||||
// put both tiles (and their grouped tiles) in the same group
|
|
||||||
const groupTiles = (gameId, tileIdx1, tileIdx2) => {
|
|
||||||
let tiles = GAMES[gameId].puzzle.tiles
|
|
||||||
let group1 = getTileGroup(gameId, tileIdx1)
|
|
||||||
let group2 = getTileGroup(gameId, tileIdx2)
|
|
||||||
|
|
||||||
let group
|
|
||||||
let searchGroups = []
|
|
||||||
if (group1) {
|
|
||||||
searchGroups.push(group1)
|
|
||||||
}
|
|
||||||
if (group2) {
|
|
||||||
searchGroups.push(group2)
|
|
||||||
}
|
|
||||||
if (group1) {
|
|
||||||
group = group1
|
|
||||||
} else if (group2) {
|
|
||||||
group = group2
|
|
||||||
} else {
|
|
||||||
let maxGroup = getMaxGroup(gameId) + 1
|
|
||||||
changeData(gameId, { maxGroup })
|
|
||||||
_dataChange()
|
|
||||||
group = getMaxGroup(gameId)
|
|
||||||
}
|
|
||||||
|
|
||||||
changeTile(gameId, tileIdx1, { group })
|
|
||||||
_tileChange(tileIdx1)
|
|
||||||
changeTile(gameId, tileIdx2, { group })
|
|
||||||
_tileChange(tileIdx2)
|
|
||||||
|
|
||||||
// TODO: strange
|
|
||||||
if (searchGroups.length > 0) {
|
|
||||||
for (let tile of tiles) {
|
|
||||||
if (searchGroups.includes(tile.group)) {
|
|
||||||
changeTile(gameId, tile.idx, { group })
|
|
||||||
_tileChange(tile.idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let [type, x, y] = input
|
|
||||||
let pos = {x, y}
|
|
||||||
if (type === 'down') {
|
|
||||||
changePlayer(gameId, playerId, { down: true })
|
|
||||||
_playerChange()
|
|
||||||
evtInfo._last_mouse_down = pos
|
|
||||||
|
|
||||||
const tileIdxAtPos = freeTileIdxByPos(gameId, pos)
|
|
||||||
if (tileIdxAtPos >= 0) {
|
|
||||||
console.log('tile: ', tileIdxAtPos)
|
|
||||||
let maxZ = getMaxZIndex(gameId) + 1
|
|
||||||
changeData(gameId, { maxZ })
|
|
||||||
_dataChange()
|
|
||||||
const tileIdxs = getGroupedTileIdxs(gameId, tileIdxAtPos)
|
|
||||||
setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId))
|
|
||||||
setTilesOwner(gameId, tileIdxs, playerId)
|
|
||||||
_tileChanges(tileIdxs)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (type === 'move') {
|
|
||||||
changePlayer(gameId, playerId, pos)
|
|
||||||
_playerChange()
|
|
||||||
|
|
||||||
if (evtInfo._last_mouse_down !== null) {
|
|
||||||
let tileIdx = getFirstOwnedTileIdx(gameId, playerId)
|
|
||||||
if (tileIdx >= 0) {
|
|
||||||
const diffX = x - evtInfo._last_mouse_down.x
|
|
||||||
const diffY = y - evtInfo._last_mouse_down.y
|
|
||||||
const diff = { x: diffX, y: diffY }
|
|
||||||
const tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
|
||||||
moveTilesDiff(gameId, tileIdxs, diff)
|
|
||||||
_tileChanges(tileIdxs)
|
|
||||||
}
|
|
||||||
|
|
||||||
evtInfo._last_mouse_down = pos
|
|
||||||
}
|
|
||||||
} else if (type === 'up') {
|
|
||||||
changePlayer(gameId, playerId, { down: false })
|
|
||||||
_playerChange()
|
|
||||||
evtInfo._last_mouse_down = null
|
|
||||||
|
|
||||||
let tileIdx = getFirstOwnedTileIdx(gameId, playerId)
|
|
||||||
if (tileIdx >= 0) {
|
|
||||||
// drop the tile(s)
|
|
||||||
let tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
|
||||||
setTilesOwner(gameId, tileIdxs, 0)
|
|
||||||
_tileChanges(tileIdxs)
|
|
||||||
|
|
||||||
// Check if the tile was dropped near the final location
|
|
||||||
let tilePos = getTilePos(gameId, tileIdx)
|
|
||||||
let finalPos = getFinalTilePos(gameId, tileIdx)
|
|
||||||
if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) {
|
|
||||||
let diff = Geometry.pointSub(finalPos, tilePos)
|
|
||||||
// Snap the tile to the final destination
|
|
||||||
moveTilesDiff(gameId, tileIdxs, diff)
|
|
||||||
finishTiles(gameId, tileIdxs)
|
|
||||||
_tileChanges(tileIdxs)
|
|
||||||
} else {
|
|
||||||
// Snap to other tiles
|
|
||||||
const check = (gameId, tileIdx, otherTileIdx, off) => {
|
|
||||||
let info = GAMES[gameId].puzzle.info
|
|
||||||
if (otherTileIdx < 0) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (areGrouped(gameId, tileIdx, otherTileIdx)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const tilePos = getTilePos(gameId, tileIdx)
|
|
||||||
const dstPos = Geometry.pointAdd(
|
|
||||||
getTilePos(gameId, otherTileIdx),
|
|
||||||
{x: off[0] * info.tileSize, y: off[1] * info.tileSize}
|
|
||||||
)
|
|
||||||
if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) {
|
|
||||||
let diff = Geometry.pointSub(dstPos, tilePos)
|
|
||||||
let tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
|
||||||
moveTilesDiff(gameId, tileIdxs, diff)
|
|
||||||
groupTiles(gameId, tileIdx, otherTileIdx)
|
|
||||||
tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
|
|
||||||
const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs)
|
|
||||||
console.log('z:' , zIndex, tileIdxs)
|
|
||||||
setTilesZIndex(gameId, tileIdxs, zIndex)
|
|
||||||
_tileChanges(tileIdxs)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let tileIdxTmp of getGroupedTileIdxs(gameId, tileIdx)) {
|
|
||||||
let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp)
|
|
||||||
if (
|
|
||||||
check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top
|
|
||||||
|| check(gameId, tileIdxTmp, othersIdxs[1], [-1, 0]) // right
|
|
||||||
|| check(gameId, tileIdxTmp, othersIdxs[2], [0, -1]) // bottom
|
|
||||||
|| check(gameId, tileIdxTmp, othersIdxs[3], [1, 0]) // left
|
|
||||||
) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// console.log(mouse)
|
|
||||||
evtInfo._last_mouse = pos
|
|
||||||
|
|
||||||
return changes
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
createGame,
|
createGame,
|
||||||
exists,
|
exists: GameCommon.exists,
|
||||||
addPlayer,
|
addPlayer: GameCommon.addPlayer,
|
||||||
addSocket,
|
addSocket: GameCommon.addSocket,
|
||||||
get,
|
get: GameCommon.get,
|
||||||
getSockets,
|
getSockets: GameCommon.getSockets,
|
||||||
handleInput,
|
handleInput: GameCommon.handleInput,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue