everything

This commit is contained in:
Zutatensuppe 2020-11-12 19:19:02 +01:00
parent 1e9abebfd3
commit d592cef494
9 changed files with 619 additions and 539 deletions

426
server/Game.js Normal file
View file

@ -0,0 +1,426 @@
import { createPuzzle } from './Puzzle.js'
import Geometry from './../common/Geometry.js'
const GAMES = {}
function exists(gameId) {
return (!!GAMES[gameId]) || false
}
async function createGame(gameId, targetTiles, image) {
GAMES[gameId] = {
puzzle: await createPuzzle(targetTiles, image),
players: {},
sockets: [],
evtInfos: {},
}
}
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.tiles_x) : -1,
// right
(_X < info.tiles_x - 1) ? (tileIdx + 1) : -1,
// bottom
(_Y < info.tiles_y - 1) ? (tileIdx + info.tiles_x) : -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 })
}
}
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
}
// 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 (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 {
createGame,
exists,
addPlayer,
addSocket,
get,
getSockets,
handleInput,
}

View file

@ -201,5 +201,5 @@ const coordsByNum = (puzzleInfo) => {
}
export {
createPuzzle
createPuzzle,
}

View file

@ -1,13 +1,18 @@
import WebSocketServer from './WebSocketServer.js'
import express from 'express'
import { createPuzzle } from './puzzle.js'
import config from './config.js'
import { uniqId, choice } from './util.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
// actual calculated number can be higher
const TARGET_TILES = 500
const TARGET_TILES = 1000
const IMAGES = [
'/example-images/ima_86ec3fa.jpeg',
@ -37,6 +42,7 @@ app.use('/g/:gid', (req, res, next) => {
`)
})
app.use('/common/', express.static('./../common/'))
app.use('/', (req, res, next) => {
if (req.path === '/') {
res.send(`
@ -63,57 +69,31 @@ const notify = (data, sockets) => {
for (let socket of sockets) {
wss.notifyOne(data, socket)
}
console.log('notify', data)
// console.log('notify', data)
}
wss.on('message', async ({socket, data}) => {
try {
const proto = socket.protocol.split('|')
const uid = proto[0]
const gid = proto[1]
const parsed = JSON.parse(data)
switch (parsed.type) {
case 'init': {
// a new player (or previous player) joined
games[gid] = games[gid] || {
puzzle: await createPuzzle(TARGET_TILES, choice(IMAGES)),
players: {},
sockets: []
const playerId = proto[0]
const gameId = proto[1]
const [type, typeData] = JSON.parse(data)
switch (type) {
case EV_CLIENT_INIT: {
if (!Game.exists(gameId)) {
await Game.createGame(gameId, TARGET_TILES, choice(IMAGES))
}
if (!games[gid].sockets.includes(socket)) {
games[gid].sockets.push(socket)
}
games[gid].players[uid] = {id: uid, x: 0, y: 0, down: false}
Game.addPlayer(gameId, playerId)
Game.addSocket(gameId, socket)
wss.notifyOne({
type: 'init',
game: {
puzzle: games[gid].puzzle,
players: games[gid].players,
},
}, socket)
wss.notifyOne([EV_SERVER_INIT, Game.get(gameId)], socket)
} break;
// somebody has changed the state
case 'state': {
for (let change of parsed.state.changes) {
switch (change.type) {
case 'change_player': {
games[gid].players[uid] = change.player
} break;
case 'change_tile': {
games[gid].puzzle.tiles[change.tile.idx] = change.tile
} break;
case 'change_data': {
games[gid].puzzle.data = change.data
} break;
}
case EV_CLIENT_MOUSE: {
const changes = Game.handleInput(gameId, playerId, typeData)
if (changes.length > 0) {
notify([EV_SERVER_STATE_CHANGED, changes], Game.getSockets(gameId))
}
notify({
type:'state_changed',
origin: uid,
changes: parsed.state.changes,
}, games[gid].sockets)
} break;
}
} catch (e) {