puzzle/src/common/GameCommon.ts

909 lines
23 KiB
TypeScript
Raw Normal View History

2021-05-17 02:32:33 +02:00
import Geometry, { Point, Rect } from './Geometry'
2021-05-17 00:27:47 +02:00
import Protocol from './Protocol'
import { Rng } from './Rng'
import Time from './Time'
import Util from './Util'
2021-05-17 02:32:33 +02:00
export type EncodedPlayer = Array<any>
export type EncodedPiece = Array<any>
export type EncodedPieceShape = number
2021-05-17 00:27:47 +02:00
2021-05-21 00:43:02 +02:00
// TODO: maybe something other than string in the future
export type Category = string
2021-05-17 00:27:47 +02:00
interface GameRng {
obj: Rng
type?: string
}
interface Game {
id: string
players: Array<EncodedPlayer>
puzzle: Puzzle
evtInfos: Record<string, EvtInfo>
2021-05-17 02:32:33 +02:00
scoreMode?: ScoreMode
2021-05-17 00:27:47 +02:00
rng: GameRng
}
2021-05-21 00:43:02 +02:00
export interface Image {
file: string
url: string
category: Category
title: string
}
export interface GameSettings {
tiles: number
image: Image
scoreMode: ScoreMode
}
2021-05-17 00:27:47 +02:00
export interface Puzzle {
tiles: Array<EncodedPiece>
data: PuzzleData
info: PuzzleInfo
}
interface PuzzleData {
started: number
finished: number
maxGroup: number
maxZ: number
}
interface PuzzleTable {
width: number
height: number
}
2021-05-17 02:32:33 +02:00
enum PieceEdge {
Flat = 0,
Out = 1,
In = -1,
}
2021-05-17 00:27:47 +02:00
export interface PieceShape {
2021-05-17 02:32:33 +02:00
top: PieceEdge
bottom: PieceEdge
left: PieceEdge
right: PieceEdge
2021-05-17 00:27:47 +02:00
}
export interface Piece {
owner: string|number
2021-05-17 02:32:33 +02:00
idx: number
pos: Point
z: number
group: number
2021-05-17 00:27:47 +02:00
}
export interface PuzzleInfo {
table: PuzzleTable
targetTiles: number,
imageUrl: string
width: number
height: number
tileSize: number
tileDrawSize: number
tileMarginWidth: number
tileDrawOffset: number
snapDistance: number
tiles: number
tilesX: number
tilesY: number
2021-05-17 02:32:33 +02:00
shapes: Array<EncodedPieceShape>
2021-05-17 00:27:47 +02:00
}
export interface Player {
id: string
x: number
y: number
d: 0|1
name: string|null
color: string|null
bgcolor: string|null
points: number
ts: number
}
interface EvtInfo {
_last_mouse: Point|null
_last_mouse_down: Point|null
}
2020-11-17 22:34:15 +01:00
2021-05-17 02:32:33 +02:00
export enum ScoreMode {
FINAL = 0,
ANY = 1,
}
2021-04-27 21:43:53 +02:00
const IDLE_TIMEOUT_SEC = 30
2021-05-17 00:27:47 +02:00
// Map<gameId, Game>
const GAMES: Record<string, Game> = {}
2020-11-17 22:34:15 +01:00
2021-05-17 00:27:47 +02:00
function exists(gameId: string) {
2020-11-17 22:34:15 +01:00
return (!!GAMES[gameId]) || false
}
2021-05-17 00:27:47 +02:00
function __createPlayerObject(id: string, ts: number): Player {
2020-12-05 19:45:34 +01:00
return {
id: id,
x: 0,
y: 0,
d: 0, // mouse down
2020-12-07 12:20:09 +01:00
name: null, // 'anon'
color: null, // '#ffffff'
bgcolor: null, // '#222222'
2020-12-05 19:45:34 +01:00
points: 0,
ts: ts,
}
}
2021-05-17 00:27:47 +02:00
function setGame(gameId: string, game: Game) {
2020-11-17 22:34:15 +01:00
GAMES[gameId] = game
}
2021-05-17 00:27:47 +02:00
function getPlayerIndexById(gameId: string, playerId: string): number {
2020-12-23 01:19:30 +01:00
let i = 0;
for (let player of GAMES[gameId].players) {
if (Util.decodePlayer(player).id === playerId) {
return i
}
i++
}
return -1
}
2021-05-17 02:32:33 +02:00
function getPlayerIdByIndex(gameId: string, playerIndex: number): string|null {
2020-12-23 01:19:30 +01:00
if (GAMES[gameId].players.length > playerIndex) {
return Util.decodePlayer(GAMES[gameId].players[playerIndex]).id
}
return null
}
2021-05-17 02:32:33 +02:00
function getPlayer(gameId: string, playerId: string): Player {
const idx = getPlayerIndexById(gameId, playerId)
2020-12-23 01:19:30 +01:00
return Util.decodePlayer(GAMES[gameId].players[idx])
2020-12-05 19:45:34 +01:00
}
2021-05-17 00:27:47 +02:00
function setPlayer(gameId: string, playerId: string, player: Player) {
2021-05-17 02:32:33 +02:00
const idx = getPlayerIndexById(gameId, playerId)
2020-12-23 01:19:30 +01:00
if (idx === -1) {
GAMES[gameId].players.push(Util.encodePlayer(player))
} else {
GAMES[gameId].players[idx] = Util.encodePlayer(player)
}
2020-12-05 19:45:34 +01:00
}
2021-05-17 00:27:47 +02:00
function setTile(gameId: string, tileIdx: number, tile: Piece) {
2020-12-05 19:45:34 +01:00
GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile)
}
2021-05-17 00:27:47 +02:00
function setPuzzleData(gameId: string, data: PuzzleData) {
2020-12-05 19:45:34 +01:00
GAMES[gameId].puzzle.data = data
}
2021-05-17 00:27:47 +02:00
function playerExists(gameId: string, playerId: string) {
2020-12-23 01:19:30 +01:00
const idx = getPlayerIndexById(gameId, playerId)
return idx !== -1
2020-12-03 21:11:52 +01:00
}
2021-05-17 02:32:33 +02:00
function getActivePlayers(gameId: string, ts: number): Array<Player> {
const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC
2021-05-17 00:27:47 +02:00
return getAllPlayers(gameId).filter((p: Player) => p.ts >= minTs)
2020-12-21 02:29:14 +01:00
}
2021-05-17 02:32:33 +02:00
function getIdlePlayers(gameId: string, ts: number): Array<Player> {
const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC
2021-05-17 00:27:47 +02:00
return getAllPlayers(gameId).filter((p: Player) => p.ts < minTs && p.points > 0)
2020-12-05 19:45:34 +01:00
}
2021-05-17 02:32:33 +02:00
function addPlayer(gameId: string, playerId: string, ts: number): void {
2020-12-23 01:19:30 +01:00
if (!playerExists(gameId, playerId)) {
2020-12-22 22:35:09 +01:00
setPlayer(gameId, playerId, __createPlayerObject(playerId, ts))
2020-11-25 22:03:35 +01:00
} else {
changePlayer(gameId, playerId, { ts })
2020-11-17 22:34:15 +01:00
}
2020-12-24 15:48:47 +01:00
}
2021-05-17 02:32:33 +02:00
function getEvtInfo(gameId: string, playerId: string): EvtInfo {
2020-12-24 15:48:47 +01:00
if (playerId in GAMES[gameId].evtInfos) {
return GAMES[gameId].evtInfos[playerId]
2020-11-17 22:34:15 +01:00
}
2020-12-24 15:48:47 +01:00
return {
_last_mouse: null,
_last_mouse_down: null,
}
}
2021-05-17 00:27:47 +02:00
function setEvtInfo(gameId: string, playerId: string, evtInfo: EvtInfo) {
2020-12-24 15:48:47 +01:00
GAMES[gameId].evtInfos[playerId] = evtInfo
2020-11-17 22:34:15 +01:00
}
2021-05-17 00:27:47 +02:00
function getAllGames(): Array<Game> {
return Object.values(GAMES).sort((a: Game, b: Game) => {
// when both have same finished state, sort by started
if (isFinished(a.id) === isFinished(b.id)) {
return b.puzzle.data.started - a.puzzle.data.started
}
// otherwise, sort: unfinished, finished
return isFinished(a.id) ? 1 : -1
})
2020-12-05 19:45:34 +01:00
}
2021-05-17 02:32:33 +02:00
function getAllPlayers(gameId: string): Array<Player> {
2020-12-05 19:45:34 +01:00
return GAMES[gameId]
2020-12-23 01:19:30 +01:00
? GAMES[gameId].players.map(Util.decodePlayer)
2020-12-05 19:45:34 +01:00
: []
2020-11-25 22:03:35 +01:00
}
2021-05-17 00:27:47 +02:00
function get(gameId: string) {
2020-11-17 22:34:15 +01:00
return GAMES[gameId]
}
2021-05-17 02:32:33 +02:00
function getTileCount(gameId: string): number {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.tiles.length
}
2021-05-17 02:32:33 +02:00
function getImageUrl(gameId: string): string {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.info.imageUrl
}
2021-05-17 00:27:47 +02:00
function setImageUrl(gameId: string, imageUrl: string) {
2021-04-21 09:54:10 +02:00
GAMES[gameId].puzzle.info.imageUrl = imageUrl
}
2021-05-17 02:32:33 +02:00
function getScoreMode(gameId: string): ScoreMode {
return GAMES[gameId].scoreMode || ScoreMode.FINAL
2021-04-27 21:43:53 +02:00
}
2021-05-17 00:27:47 +02:00
function isFinished(gameId: string) {
return getFinishedTileCount(gameId) === getTileCount(gameId)
}
2021-05-17 00:27:47 +02:00
function getFinishedTileCount(gameId: string) {
2020-12-05 19:45:34 +01:00
let count = 0
for (let t of GAMES[gameId].puzzle.tiles) {
if (Util.decodeTile(t).owner === -1) {
count++
}
}
return count
}
2021-05-17 00:27:47 +02:00
function getTilesSortedByZIndex(gameId: string) {
2020-12-05 19:45:34 +01:00
const tiles = GAMES[gameId].puzzle.tiles.map(Util.decodeTile)
return tiles.sort((t1, t2) => t1.z - t2.z)
}
2021-05-17 00:27:47 +02:00
function changePlayer(gameId: string, playerId: string, change: any) {
2020-12-05 19:45:34 +01:00
const player = getPlayer(gameId, playerId)
2020-11-17 22:34:15 +01:00
for (let k of Object.keys(change)) {
2021-05-17 02:32:33 +02:00
// @ts-ignore
2020-12-05 19:45:34 +01:00
player[k] = change[k]
2020-11-17 22:34:15 +01:00
}
2020-12-05 19:45:34 +01:00
setPlayer(gameId, playerId, player)
2020-11-17 22:34:15 +01:00
}
2021-05-17 00:27:47 +02:00
function changeData(gameId: string, change: any) {
2020-11-17 22:34:15 +01:00
for (let k of Object.keys(change)) {
2021-05-17 00:27:47 +02:00
// @ts-ignore
2020-11-17 22:34:15 +01:00
GAMES[gameId].puzzle.data[k] = change[k]
}
}
2021-05-17 00:27:47 +02:00
function changeTile(gameId: string, tileIdx: number, change: any) {
2020-11-17 22:34:15 +01:00
for (let k of Object.keys(change)) {
2020-12-05 19:45:34 +01:00
const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx])
2021-05-17 02:32:33 +02:00
// @ts-ignore
2020-12-05 19:45:34 +01:00
tile[k] = change[k]
GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile)
2020-11-17 22:34:15 +01:00
}
}
2021-05-17 02:32:33 +02:00
const getTile = (gameId: string, tileIdx: number): Piece => {
2020-12-05 19:45:34 +01:00
return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx])
2020-11-17 22:34:15 +01:00
}
2021-05-17 00:27:47 +02:00
const getTileGroup = (gameId: string, tileIdx: number) => {
2020-11-17 22:34:15 +01:00
const tile = getTile(gameId, tileIdx)
return tile.group
}
2021-05-17 00:27:47 +02:00
const getFinalTilePos = (gameId: string, tileIdx: number) => {
2020-11-17 22:34:15 +01:00
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)
}
2021-05-17 00:27:47 +02:00
const getTilePos = (gameId: string, tileIdx: number) => {
2020-11-17 22:34:15 +01:00
const tile = getTile(gameId, tileIdx)
return tile.pos
}
2021-04-20 23:02:44 +02:00
// todo: instead, just make the table bigger and use that :)
2021-05-17 00:27:47 +02:00
const getBounds = (gameId: string) => {
2021-04-20 23:02:44 +02:00
const tw = getTableWidth(gameId)
const th = getTableHeight(gameId)
const overX = Math.round(tw / 4)
const overY = Math.round(th / 4)
return {
x: 0 - overX,
y: 0 - overY,
w: tw + 2 * overX,
h: th + 2 * overY,
}
}
2021-05-17 00:27:47 +02:00
const getTileBounds = (gameId: string, tileIdx: number) => {
2021-04-20 23:02:44 +02:00
const s = getTileSize(gameId)
const tile = getTile(gameId, tileIdx)
return {
x: tile.pos.x,
y: tile.pos.y,
w: s,
h: s,
}
}
2021-05-17 00:27:47 +02:00
const getTileZIndex = (gameId: string, tileIdx: number) => {
2020-11-17 22:34:15 +01:00
const tile = getTile(gameId, tileIdx)
return tile.z
}
2021-05-17 00:27:47 +02:00
const getFirstOwnedTileIdx = (gameId: string, playerId: string) => {
2020-11-17 22:34:15 +01:00
for (let t of GAMES[gameId].puzzle.tiles) {
2020-12-05 19:45:34 +01:00
const tile = Util.decodeTile(t)
2021-05-17 00:27:47 +02:00
if (tile.owner === playerId) {
2020-12-05 19:45:34 +01:00
return tile.idx
2020-11-17 22:34:15 +01:00
}
}
return -1
}
2021-05-17 00:27:47 +02:00
const getFirstOwnedTile = (gameId: string, playerId: string) => {
const idx = getFirstOwnedTileIdx(gameId, playerId)
2020-12-05 19:45:34 +01:00
return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]
}
2021-05-17 00:27:47 +02:00
const getTileDrawOffset = (gameId: string) => {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.info.tileDrawOffset
}
2021-05-17 00:27:47 +02:00
const getTileDrawSize = (gameId: string) => {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.info.tileDrawSize
}
2021-05-17 00:27:47 +02:00
const getTileSize = (gameId: string) => {
2021-04-20 23:02:44 +02:00
return GAMES[gameId].puzzle.info.tileSize
}
2021-05-17 00:27:47 +02:00
const getStartTs = (gameId: string) => {
2020-12-07 02:38:07 +01:00
return GAMES[gameId].puzzle.data.started
}
2021-05-17 00:27:47 +02:00
const getFinishTs = (gameId: string) => {
2020-12-07 02:38:07 +01:00
return GAMES[gameId].puzzle.data.finished
}
2021-05-17 00:27:47 +02:00
const getMaxGroup = (gameId: string) => {
2020-11-17 22:34:15 +01:00
return GAMES[gameId].puzzle.data.maxGroup
}
2021-05-17 00:27:47 +02:00
const getMaxZIndex = (gameId: string) => {
2020-11-17 22:34:15 +01:00
return GAMES[gameId].puzzle.data.maxZ
}
2021-05-17 00:27:47 +02:00
const getMaxZIndexByTileIdxs = (gameId: string, tileIdxs: Array<number>) => {
2020-11-17 22:34:15 +01:00
let maxZ = 0
for (let tileIdx of tileIdxs) {
let tileZIndex = getTileZIndex(gameId, tileIdx)
if (tileZIndex > maxZ) {
maxZ = tileZIndex
}
}
return maxZ
}
2021-05-17 00:27:47 +02:00
function srcPosByTileIdx(gameId: string, tileIdx: number) {
2020-11-17 22:34:15 +01:00
const info = GAMES[gameId].puzzle.info
2020-12-05 19:45:34 +01:00
const c = Util.coordByTileIdx(info, tileIdx)
2020-11-17 22:34:15 +01:00
const cx = c.x * info.tileSize
const cy = c.y * info.tileSize
return { x: cx, y: cy }
}
2021-05-17 00:27:47 +02:00
function getSurroundingTilesByIdx(gameId: string, tileIdx: number) {
2020-11-17 22:34:15 +01:00
const info = GAMES[gameId].puzzle.info
2020-12-05 19:45:34 +01:00
const c = Util.coordByTileIdx(info, tileIdx)
2020-11-17 22:34:15 +01:00
return [
// top
2020-12-05 19:45:34 +01:00
(c.y > 0) ? (tileIdx - info.tilesX) : -1,
2020-11-17 22:34:15 +01:00
// right
2020-12-05 19:45:34 +01:00
(c.x < info.tilesX - 1) ? (tileIdx + 1) : -1,
2020-11-17 22:34:15 +01:00
// bottom
2020-12-05 19:45:34 +01:00
(c.y < info.tilesY - 1) ? (tileIdx + info.tilesX) : -1,
2020-11-17 22:34:15 +01:00
// left
2020-12-05 19:45:34 +01:00
(c.x > 0) ? (tileIdx - 1) : -1,
2020-11-17 22:34:15 +01:00
]
}
2021-05-17 00:27:47 +02:00
const setTilesZIndex = (gameId: string, tileIdxs: Array<number>, zIndex: number) => {
2020-11-17 22:34:15 +01:00
for (let tilesIdx of tileIdxs) {
changeTile(gameId, tilesIdx, { z: zIndex })
}
}
2021-05-17 00:27:47 +02:00
const moveTileDiff = (gameId: string, tileIdx: number, diff: Point) => {
2020-11-17 22:34:15 +01:00
const oldPos = getTilePos(gameId, tileIdx)
const pos = Geometry.pointAdd(oldPos, diff)
changeTile(gameId, tileIdx, { pos })
}
2021-05-17 00:27:47 +02:00
const moveTilesDiff = (gameId: string, tileIdxs: Array<number>, diff: Point) => {
2021-04-20 23:02:44 +02:00
const tileDrawSize = getTileDrawSize(gameId)
const bounds = getBounds(gameId)
const cappedDiff = diff
2020-11-17 22:34:15 +01:00
for (let tileIdx of tileIdxs) {
2021-04-20 23:02:44 +02:00
const t = getTile(gameId, tileIdx)
if (t.pos.x + diff.x < bounds.x) {
cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x)
} else if (t.pos.x + tileDrawSize + diff.x > bounds.x + bounds.w) {
cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + tileDrawSize, cappedDiff.x)
}
if (t.pos.y + diff.y < bounds.y) {
cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y)
} else if (t.pos.y + tileDrawSize + diff.y > bounds.y + bounds.h) {
cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + tileDrawSize, cappedDiff.y)
}
}
for (let tileIdx of tileIdxs) {
moveTileDiff(gameId, tileIdx, cappedDiff)
2020-11-17 22:34:15 +01:00
}
}
2021-05-17 00:27:47 +02:00
const finishTiles = (gameId: string, tileIdxs: Array<number>) => {
2020-11-17 22:34:15 +01:00
for (let tileIdx of tileIdxs) {
changeTile(gameId, tileIdx, { owner: -1, z: 1 })
}
}
2021-05-17 00:27:47 +02:00
const setTilesOwner = (
gameId: string,
tileIdxs: Array<number>,
owner: string|number
) => {
2020-11-17 22:34:15 +01:00
for (let tileIdx of tileIdxs) {
changeTile(gameId, tileIdx, { owner })
}
}
// get all grouped tiles for a tile
2021-05-17 00:27:47 +02:00
function getGroupedTileIdxs(gameId: string, tileIdx: number) {
2020-11-17 22:34:15 +01:00
const tiles = GAMES[gameId].puzzle.tiles
2020-12-05 19:45:34 +01:00
const tile = Util.decodeTile(tiles[tileIdx])
2020-11-17 22:34:15 +01:00
const grouped = []
if (tile.group) {
for (let other of tiles) {
2020-12-05 19:45:34 +01:00
const otherTile = Util.decodeTile(other)
if (otherTile.group === tile.group) {
grouped.push(otherTile.idx)
2020-11-17 22:34:15 +01:00
}
}
} 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
2021-05-17 00:27:47 +02:00
const freeTileIdxByPos = (gameId: string, pos: Point) => {
2020-11-17 22:34:15 +01:00
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++) {
2020-12-05 19:45:34 +01:00
const tile = Util.decodeTile(tiles[idx])
2020-11-17 22:34:15 +01:00
if (tile.owner !== 0) {
continue
}
2021-05-17 02:32:33 +02:00
const collisionRect: Rect = {
2020-11-17 22:34:15 +01:00
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
}
2021-05-17 00:27:47 +02:00
const getPlayerBgColor = (gameId: string, playerId: string) => {
2020-12-22 22:35:09 +01:00
const p = getPlayer(gameId, playerId)
return p ? p.bgcolor : null
2020-12-05 19:45:34 +01:00
}
2021-05-17 00:27:47 +02:00
const getPlayerColor = (gameId: string, playerId: string) => {
2020-12-22 22:35:09 +01:00
const p = getPlayer(gameId, playerId)
return p ? p.color : null
2020-12-05 19:45:34 +01:00
}
2021-05-17 00:27:47 +02:00
const getPlayerName = (gameId: string, playerId: string) => {
2020-12-22 22:35:09 +01:00
const p = getPlayer(gameId, playerId)
return p ? p.name : null
2020-12-05 19:45:34 +01:00
}
2021-05-17 02:32:33 +02:00
const getPlayerPoints = (gameId: string, playerId: string): number => {
2020-12-22 22:35:09 +01:00
const p = getPlayer(gameId, playerId)
2021-05-17 02:32:33 +02:00
return p ? p.points : 0
2020-12-05 19:45:34 +01:00
}
2020-11-17 22:34:15 +01:00
// determine if two tiles are grouped together
2021-05-17 00:27:47 +02:00
const areGrouped = (gameId: string, tileIdx1: number, tileIdx2: number) => {
2020-11-17 22:34:15 +01:00
const g1 = getTileGroup(gameId, tileIdx1)
const g2 = getTileGroup(gameId, tileIdx2)
return g1 && g1 === g2
}
2021-05-17 00:27:47 +02:00
const getTableWidth = (gameId: string) => {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.info.table.width
}
2021-05-17 00:27:47 +02:00
const getTableHeight = (gameId: string) => {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.info.table.height
}
2021-05-17 00:27:47 +02:00
const getPuzzle = (gameId: string) => {
2020-12-22 22:35:09 +01:00
return GAMES[gameId].puzzle
}
2021-05-17 00:27:47 +02:00
const getRng = (gameId: string): Rng => {
2020-12-22 22:35:09 +01:00
return GAMES[gameId].rng.obj
}
2021-05-17 00:27:47 +02:00
const getPuzzleWidth = (gameId: string) => {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.info.width
}
2021-05-17 00:27:47 +02:00
const getPuzzleHeight = (gameId: string) => {
2020-12-05 19:45:34 +01:00
return GAMES[gameId].puzzle.info.height
}
2021-05-17 00:27:47 +02:00
function handleInput(gameId: string, playerId: string, input: any, ts: number) {
2020-12-05 19:45:34 +01:00
const puzzle = GAMES[gameId].puzzle
2021-05-09 21:43:35 +02:00
const evtInfo = getEvtInfo(gameId, playerId)
2020-11-17 22:34:15 +01:00
2021-05-17 00:27:47 +02:00
const changes = [] as Array<Array<any>>
2020-11-17 22:34:15 +01:00
const _dataChange = () => {
changes.push([Protocol.CHANGE_DATA, puzzle.data])
2020-11-17 22:34:15 +01:00
}
2021-05-17 00:27:47 +02:00
const _tileChange = (tileIdx: number) => {
2021-05-09 21:43:35 +02:00
changes.push([
Protocol.CHANGE_TILE,
Util.encodeTile(getTile(gameId, tileIdx)),
])
2020-11-17 22:34:15 +01:00
}
2021-05-17 00:27:47 +02:00
const _tileChanges = (tileIdxs: Array<number>) => {
2021-05-09 21:43:35 +02:00
for (const tileIdx of tileIdxs) {
2020-11-17 22:34:15 +01:00
_tileChange(tileIdx)
}
}
const _playerChange = () => {
2021-05-09 21:43:35 +02:00
changes.push([
Protocol.CHANGE_PLAYER,
Util.encodePlayer(getPlayer(gameId, playerId)),
])
2020-11-17 22:34:15 +01:00
}
// put both tiles (and their grouped tiles) in the same group
2021-05-17 00:27:47 +02:00
const groupTiles = (gameId: string, tileIdx1: number, tileIdx2: number) => {
2021-05-09 21:43:35 +02:00
const tiles = GAMES[gameId].puzzle.tiles
const group1 = getTileGroup(gameId, tileIdx1)
const group2 = getTileGroup(gameId, tileIdx2)
2020-11-17 22:34:15 +01:00
let group
2021-05-09 21:43:35 +02:00
const searchGroups = []
2020-11-17 22:34:15 +01:00
if (group1) {
searchGroups.push(group1)
}
if (group2) {
searchGroups.push(group2)
}
if (group1) {
group = group1
} else if (group2) {
group = group2
} else {
2021-05-09 21:43:35 +02:00
const maxGroup = getMaxGroup(gameId) + 1
2020-11-17 22:34:15 +01:00
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) {
2021-05-09 21:43:35 +02:00
for (const t of tiles) {
2020-12-05 19:45:34 +01:00
const tile = Util.decodeTile(t)
2020-11-17 22:34:15 +01:00
if (searchGroups.includes(tile.group)) {
changeTile(gameId, tile.idx, { group })
_tileChange(tile.idx)
}
}
}
}
2020-11-25 22:03:35 +01:00
const type = input[0]
2020-12-23 01:19:30 +01:00
if (type === Protocol.INPUT_EV_BG_COLOR) {
2020-12-03 21:24:59 +01:00
const bgcolor = input[1]
changePlayer(gameId, playerId, { bgcolor, ts })
_playerChange()
2020-12-23 01:19:30 +01:00
} else if (type === Protocol.INPUT_EV_PLAYER_COLOR) {
2020-11-25 22:03:35 +01:00
const color = input[1]
changePlayer(gameId, playerId, { color, ts })
_playerChange()
2020-12-23 01:19:30 +01:00
} else if (type === Protocol.INPUT_EV_PLAYER_NAME) {
2020-11-25 22:03:35 +01:00
const name = `${input[1]}`.substr(0, 16)
changePlayer(gameId, playerId, { name, ts })
_playerChange()
2020-12-23 01:19:30 +01:00
} else if (type === Protocol.INPUT_EV_MOUSE_DOWN) {
2020-11-25 22:03:35 +01:00
const x = input[1]
const y = input[2]
const pos = {x, y}
changePlayer(gameId, playerId, { d: 1, ts })
2020-11-17 22:34:15 +01:00
_playerChange()
evtInfo._last_mouse_down = pos
const tileIdxAtPos = freeTileIdxByPos(gameId, pos)
if (tileIdxAtPos >= 0) {
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)
}
2020-11-25 22:03:35 +01:00
evtInfo._last_mouse = pos
2020-11-17 22:34:15 +01:00
2020-12-23 01:19:30 +01:00
} else if (type === Protocol.INPUT_EV_MOUSE_MOVE) {
2020-11-25 22:03:35 +01:00
const x = input[1]
const y = input[2]
const pos = {x, y}
2020-12-24 15:48:47 +01:00
if (evtInfo._last_mouse_down === null) {
// player is just moving the hand
changePlayer(gameId, playerId, {x, y, ts})
_playerChange()
} else {
2020-11-17 22:34:15 +01:00
let tileIdx = getFirstOwnedTileIdx(gameId, playerId)
if (tileIdx >= 0) {
2020-12-24 15:48:47 +01:00
// player is moving a tile (and hand)
changePlayer(gameId, playerId, {x, y, ts})
_playerChange()
2021-04-20 23:02:44 +02:00
// check if pos is on the tile, otherwise dont move
// (mouse could be out of table, but tile stays on it)
2020-11-17 22:34:15 +01:00
const tileIdxs = getGroupedTileIdxs(gameId, tileIdx)
2021-04-20 23:02:44 +02:00
let anyOk = Geometry.pointInBounds(pos, getBounds(gameId))
&& Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId))
for (let idx of tileIdxs) {
const bounds = getTileBounds(gameId, idx)
if (Geometry.pointInBounds(pos, bounds)) {
anyOk = true
break
}
}
if (anyOk) {
const diffX = x - evtInfo._last_mouse_down.x
const diffY = y - evtInfo._last_mouse_down.y
const diff = { x: diffX, y: diffY }
moveTilesDiff(gameId, tileIdxs, diff)
_tileChanges(tileIdxs)
}
2020-12-24 15:48:47 +01:00
} else {
// player is just moving map, so no change in position!
changePlayer(gameId, playerId, {ts})
_playerChange()
2020-11-17 22:34:15 +01:00
}
evtInfo._last_mouse_down = pos
}
2020-11-25 22:03:35 +01:00
evtInfo._last_mouse = pos
2020-12-23 01:19:30 +01:00
} else if (type === Protocol.INPUT_EV_MOUSE_UP) {
2020-11-25 22:03:35 +01:00
const x = input[1]
const y = input[2]
const pos = {x, y}
const d = 0
2020-11-25 22:03:35 +01:00
2020-11-17 22:34:15 +01:00
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)
2021-04-27 21:43:53 +02:00
let points = getPlayerPoints(gameId, playerId)
2021-05-17 02:32:33 +02:00
if (getScoreMode(gameId) === ScoreMode.FINAL) {
points += tileIdxs.length
2021-05-17 02:32:33 +02:00
} else if (getScoreMode(gameId) === ScoreMode.ANY) {
points += 1
2021-04-27 21:43:53 +02:00
} else {
// no score mode... should never occur, because there is a
2021-05-17 02:32:33 +02:00
// fallback to ScoreMode.FINAL in getScoreMode function
2021-04-27 21:43:53 +02:00
}
changePlayer(gameId, playerId, { d, ts, points })
_playerChange()
2021-04-27 21:43:53 +02:00
// check if the puzzle is finished
if (getFinishedTileCount(gameId) === getTileCount(gameId)) {
2020-12-22 22:35:09 +01:00
changeData(gameId, { finished: ts })
_dataChange()
}
2020-11-17 22:34:15 +01:00
} else {
// Snap to other tiles
2021-05-17 00:27:47 +02:00
const check = (
gameId: string,
tileIdx: number,
otherTileIdx: number,
off: Array<number>
) => {
2020-11-17 22:34:15 +01:00
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)
setTilesZIndex(gameId, tileIdxs, zIndex)
_tileChanges(tileIdxs)
return true
}
return false
}
let snapped = false
2020-11-17 22:34:15 +01:00
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
) {
snapped = true
2020-11-17 22:34:15 +01:00
break
}
}
2021-05-17 02:32:33 +02:00
if (snapped && getScoreMode(gameId) === ScoreMode.ANY) {
const points = getPlayerPoints(gameId, playerId) + 1
changePlayer(gameId, playerId, { d, ts, points })
_playerChange()
} else {
changePlayer(gameId, playerId, { d, ts })
_playerChange()
}
2020-11-17 22:34:15 +01:00
}
} else {
changePlayer(gameId, playerId, { d, ts })
_playerChange()
2020-11-17 22:34:15 +01:00
}
2020-11-25 22:03:35 +01:00
evtInfo._last_mouse = pos
} else if (type === Protocol.INPUT_EV_ZOOM_IN) {
const x = input[1]
const y = input[2]
changePlayer(gameId, playerId, { x, y, ts })
_playerChange()
evtInfo._last_mouse = { x, y }
} else if (type === Protocol.INPUT_EV_ZOOM_OUT) {
const x = input[1]
const y = input[2]
changePlayer(gameId, playerId, { x, y, ts })
_playerChange()
evtInfo._last_mouse = { x, y }
2020-11-25 22:03:35 +01:00
} else {
changePlayer(gameId, playerId, { ts })
_playerChange()
2020-11-17 22:34:15 +01:00
}
2020-12-24 15:48:47 +01:00
setEvtInfo(gameId, playerId, evtInfo)
2020-11-17 22:34:15 +01:00
return changes
}
export default {
2020-12-22 22:35:09 +01:00
__createPlayerObject,
2021-04-30 01:03:43 +02:00
setGame,
2020-11-17 22:34:15 +01:00
exists,
2020-12-03 21:11:52 +01:00
playerExists,
2020-12-05 19:45:34 +01:00
getActivePlayers,
getIdlePlayers,
2020-11-17 22:34:15 +01:00
addPlayer,
2020-12-05 19:45:34 +01:00
getFinishedTileCount,
getTileCount,
getImageUrl,
2021-04-21 09:54:10 +02:00
setImageUrl,
2020-11-17 22:34:15 +01:00
get,
2020-11-25 22:03:35 +01:00
getAllGames,
2020-12-05 19:45:34 +01:00
getPlayerBgColor,
getPlayerColor,
getPlayerName,
2020-12-23 01:19:30 +01:00
getPlayerIndexById,
getPlayerIdByIndex,
2020-12-05 19:45:34 +01:00
changePlayer,
setPlayer,
setTile,
setPuzzleData,
getTableWidth,
getTableHeight,
2020-12-22 22:35:09 +01:00
getPuzzle,
getRng,
2020-12-05 19:45:34 +01:00
getPuzzleWidth,
getPuzzleHeight,
getTilesSortedByZIndex,
getFirstOwnedTile,
getTileDrawOffset,
getTileDrawSize,
2020-12-24 13:56:14 +01:00
getFinalTilePos,
2020-12-07 02:38:07 +01:00
getStartTs,
getFinishTs,
2020-11-17 22:34:15 +01:00
handleInput,
}