add linting, do more type hinting
This commit is contained in:
parent
46f3fc7480
commit
d4f02c10df
29 changed files with 3353 additions and 1354 deletions
|
|
@ -1,172 +1,31 @@
|
|||
import Geometry, { Point, Rect } from './Geometry'
|
||||
import Protocol from './Protocol'
|
||||
import { Rng, RngSerialized } from './Rng'
|
||||
import { Rng } from './Rng'
|
||||
import Time from './Time'
|
||||
import { FixedLengthArray } from './Types'
|
||||
import Util from './Util'
|
||||
|
||||
export type Timestamp = number
|
||||
|
||||
export type EncodedPlayer = FixedLengthArray<[
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
0|1,
|
||||
string|null,
|
||||
string|null,
|
||||
string|null,
|
||||
number,
|
||||
Timestamp,
|
||||
]>
|
||||
|
||||
export type EncodedPiece = FixedLengthArray<[
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
string|number,
|
||||
number,
|
||||
]>
|
||||
|
||||
export type EncodedPieceShape = number
|
||||
|
||||
export type EncodedGame = FixedLengthArray<[
|
||||
string,
|
||||
string,
|
||||
RngSerialized,
|
||||
import {
|
||||
Change,
|
||||
EncodedPiece,
|
||||
EvtInfo,
|
||||
Game,
|
||||
Input,
|
||||
Piece,
|
||||
PieceChange,
|
||||
Player,
|
||||
PlayerChange,
|
||||
Puzzle,
|
||||
Array<EncodedPlayer>,
|
||||
Record<string, EvtInfo>,
|
||||
PuzzleData,
|
||||
PuzzleDataChange,
|
||||
ScoreMode,
|
||||
]>
|
||||
|
||||
export interface ReplayData {
|
||||
log: any[],
|
||||
game: EncodedGame|null
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: number
|
||||
slug: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface GameRng {
|
||||
obj: Rng
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
id: string
|
||||
players: Array<EncodedPlayer>
|
||||
puzzle: Puzzle
|
||||
evtInfos: Record<string, EvtInfo>
|
||||
scoreMode?: ScoreMode
|
||||
rng: GameRng
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id: number
|
||||
filename: string
|
||||
file: string
|
||||
url: string
|
||||
title: string
|
||||
tags: Array<Tag>
|
||||
created: number
|
||||
}
|
||||
|
||||
export interface GameSettings {
|
||||
tiles: number
|
||||
image: Image
|
||||
scoreMode: ScoreMode
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
enum PieceEdge {
|
||||
Flat = 0,
|
||||
Out = 1,
|
||||
In = -1,
|
||||
}
|
||||
export interface PieceShape {
|
||||
top: PieceEdge
|
||||
bottom: PieceEdge
|
||||
left: PieceEdge
|
||||
right: PieceEdge
|
||||
}
|
||||
|
||||
export interface Piece {
|
||||
owner: string|number
|
||||
idx: number
|
||||
pos: Point
|
||||
z: number
|
||||
group: number
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
shapes: Array<EncodedPieceShape>
|
||||
}
|
||||
|
||||
export interface Player {
|
||||
id: string
|
||||
x: number
|
||||
y: number
|
||||
d: 0|1
|
||||
name: string|null
|
||||
color: string|null
|
||||
bgcolor: string|null
|
||||
points: number
|
||||
ts: Timestamp
|
||||
}
|
||||
|
||||
interface EvtInfo {
|
||||
_last_mouse: Point|null
|
||||
_last_mouse_down: Point|null
|
||||
}
|
||||
|
||||
export enum ScoreMode {
|
||||
FINAL = 0,
|
||||
ANY = 1,
|
||||
}
|
||||
Timestamp
|
||||
} from './Types'
|
||||
import Util from './Util'
|
||||
|
||||
const IDLE_TIMEOUT_SEC = 30
|
||||
|
||||
// Map<gameId, Game>
|
||||
const GAMES: Record<string, Game> = {}
|
||||
|
||||
function exists(gameId: string) {
|
||||
function exists(gameId: string): boolean {
|
||||
return (!!GAMES[gameId]) || false
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +49,7 @@ function setGame(gameId: string, game: Game): void {
|
|||
|
||||
function getPlayerIndexById(gameId: string, playerId: string): number {
|
||||
let i = 0;
|
||||
for (let player of GAMES[gameId].players) {
|
||||
for (const player of GAMES[gameId].players) {
|
||||
if (Util.decodePlayer(player).id === playerId) {
|
||||
return i
|
||||
}
|
||||
|
|
@ -293,8 +152,8 @@ function getAllPlayers(gameId: string): Array<Player> {
|
|||
: []
|
||||
}
|
||||
|
||||
function get(gameId: string) {
|
||||
return GAMES[gameId]
|
||||
function get(gameId: string): Game|null {
|
||||
return GAMES[gameId] || null
|
||||
}
|
||||
|
||||
function getPieceCount(gameId: string): number {
|
||||
|
|
@ -319,7 +178,7 @@ function isFinished(gameId: string): boolean {
|
|||
|
||||
function getFinishedPiecesCount(gameId: string): number {
|
||||
let count = 0
|
||||
for (let t of GAMES[gameId].puzzle.tiles) {
|
||||
for (const t of GAMES[gameId].puzzle.tiles) {
|
||||
if (Util.decodePiece(t).owner === -1) {
|
||||
count++
|
||||
}
|
||||
|
|
@ -335,29 +194,33 @@ function getPiecesSortedByZIndex(gameId: string): Piece[] {
|
|||
function changePlayer(
|
||||
gameId: string,
|
||||
playerId: string,
|
||||
change: any
|
||||
change: PlayerChange
|
||||
): void {
|
||||
const player = getPlayer(gameId, playerId)
|
||||
if (player === null) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let k of Object.keys(change)) {
|
||||
for (const k of Object.keys(change)) {
|
||||
// @ts-ignore
|
||||
player[k] = change[k]
|
||||
}
|
||||
setPlayer(gameId, playerId, player)
|
||||
}
|
||||
|
||||
function changeData(gameId: string, change: any): void {
|
||||
for (let k of Object.keys(change)) {
|
||||
function changeData(gameId: string, change: PuzzleDataChange): void {
|
||||
for (const k of Object.keys(change)) {
|
||||
// @ts-ignore
|
||||
GAMES[gameId].puzzle.data[k] = change[k]
|
||||
}
|
||||
}
|
||||
|
||||
function changeTile(gameId: string, pieceIdx: number, change: any): void {
|
||||
for (let k of Object.keys(change)) {
|
||||
function changePiece(
|
||||
gameId: string,
|
||||
pieceIdx: number,
|
||||
change: PieceChange
|
||||
): void {
|
||||
for (const k of Object.keys(change)) {
|
||||
const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx])
|
||||
// @ts-ignore
|
||||
piece[k] = change[k]
|
||||
|
|
@ -415,13 +278,12 @@ const getPieceBounds = (gameId: string, tileIdx: number): Rect => {
|
|||
}
|
||||
}
|
||||
|
||||
const getTileZIndex = (gameId: string, tileIdx: number): number => {
|
||||
const tile = getPiece(gameId, tileIdx)
|
||||
return tile.z
|
||||
const getPieceZIndex = (gameId: string, pieceIdx: number): number => {
|
||||
return getPiece(gameId, pieceIdx).z
|
||||
}
|
||||
|
||||
const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => {
|
||||
for (let t of GAMES[gameId].puzzle.tiles) {
|
||||
for (const t of GAMES[gameId].puzzle.tiles) {
|
||||
const tile = Util.decodePiece(t)
|
||||
if (tile.owner === playerId) {
|
||||
return tile.idx
|
||||
|
|
@ -430,7 +292,10 @@ const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => {
|
|||
return -1
|
||||
}
|
||||
|
||||
const getFirstOwnedPiece = (gameId: string, playerId: string): EncodedPiece|null => {
|
||||
const getFirstOwnedPiece = (
|
||||
gameId: string,
|
||||
playerId: string
|
||||
): EncodedPiece|null => {
|
||||
const idx = getFirstOwnedPieceIdx(gameId, playerId)
|
||||
return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]
|
||||
}
|
||||
|
|
@ -463,12 +328,12 @@ const getMaxZIndex = (gameId: string): number => {
|
|||
return GAMES[gameId].puzzle.data.maxZ
|
||||
}
|
||||
|
||||
const getMaxZIndexByTileIdxs = (gameId: string, tileIdxs: Array<number>): number => {
|
||||
const getMaxZIndexByPieceIdxs = (gameId: string, pieceIdxs: Array<number>): number => {
|
||||
let maxZ = 0
|
||||
for (let tileIdx of tileIdxs) {
|
||||
let tileZIndex = getTileZIndex(gameId, tileIdx)
|
||||
if (tileZIndex > maxZ) {
|
||||
maxZ = tileZIndex
|
||||
for (const pieceIdx of pieceIdxs) {
|
||||
const curZ = getPieceZIndex(gameId, pieceIdx)
|
||||
if (curZ > maxZ) {
|
||||
maxZ = curZ
|
||||
}
|
||||
}
|
||||
return maxZ
|
||||
|
|
@ -477,7 +342,7 @@ const getMaxZIndexByTileIdxs = (gameId: string, tileIdxs: Array<number>): number
|
|||
function srcPosByTileIdx(gameId: string, tileIdx: number): Point {
|
||||
const info = GAMES[gameId].puzzle.info
|
||||
|
||||
const c = Util.coordByTileIdx(info, tileIdx)
|
||||
const c = Util.coordByPieceIdx(info, tileIdx)
|
||||
const cx = c.x * info.tileSize
|
||||
const cy = c.y * info.tileSize
|
||||
|
||||
|
|
@ -487,7 +352,7 @@ function srcPosByTileIdx(gameId: string, tileIdx: number): Point {
|
|||
function getSurroundingTilesByIdx(gameId: string, tileIdx: number) {
|
||||
const info = GAMES[gameId].puzzle.info
|
||||
|
||||
const c = Util.coordByTileIdx(info, tileIdx)
|
||||
const c = Util.coordByPieceIdx(info, tileIdx)
|
||||
|
||||
return [
|
||||
// top
|
||||
|
|
@ -501,29 +366,29 @@ function getSurroundingTilesByIdx(gameId: string, tileIdx: number) {
|
|||
]
|
||||
}
|
||||
|
||||
const setTilesZIndex = (gameId: string, tileIdxs: Array<number>, zIndex: number): void => {
|
||||
for (let tilesIdx of tileIdxs) {
|
||||
changeTile(gameId, tilesIdx, { z: zIndex })
|
||||
const setPiecesZIndex = (gameId: string, tileIdxs: Array<number>, zIndex: number): void => {
|
||||
for (const tilesIdx of tileIdxs) {
|
||||
changePiece(gameId, tilesIdx, { z: zIndex })
|
||||
}
|
||||
}
|
||||
|
||||
const moveTileDiff = (gameId: string, tileIdx: number, diff: Point): void => {
|
||||
const oldPos = getPiecePos(gameId, tileIdx)
|
||||
const pos = Geometry.pointAdd(oldPos, diff)
|
||||
changeTile(gameId, tileIdx, { pos })
|
||||
changePiece(gameId, tileIdx, { pos })
|
||||
}
|
||||
|
||||
const moveTilesDiff = (
|
||||
const movePiecesDiff = (
|
||||
gameId: string,
|
||||
tileIdxs: Array<number>,
|
||||
pieceIdxs: Array<number>,
|
||||
diff: Point
|
||||
): void => {
|
||||
const drawSize = getPieceDrawSize(gameId)
|
||||
const bounds = getBounds(gameId)
|
||||
const cappedDiff = diff
|
||||
|
||||
for (let tileIdx of tileIdxs) {
|
||||
const t = getPiece(gameId, tileIdx)
|
||||
for (const pieceIdx of pieceIdxs) {
|
||||
const t = getPiece(gameId, pieceIdx)
|
||||
if (t.pos.x + diff.x < bounds.x) {
|
||||
cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x)
|
||||
} else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) {
|
||||
|
|
@ -536,24 +401,24 @@ const moveTilesDiff = (
|
|||
}
|
||||
}
|
||||
|
||||
for (let tileIdx of tileIdxs) {
|
||||
moveTileDiff(gameId, tileIdx, cappedDiff)
|
||||
for (const pieceIdx of pieceIdxs) {
|
||||
moveTileDiff(gameId, pieceIdx, cappedDiff)
|
||||
}
|
||||
}
|
||||
|
||||
const finishTiles = (gameId: string, tileIdxs: Array<number>): void => {
|
||||
for (let tileIdx of tileIdxs) {
|
||||
changeTile(gameId, tileIdx, { owner: -1, z: 1 })
|
||||
const finishPieces = (gameId: string, pieceIdxs: Array<number>): void => {
|
||||
for (const pieceIdx of pieceIdxs) {
|
||||
changePiece(gameId, pieceIdx, { owner: -1, z: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
const setTilesOwner = (
|
||||
gameId: string,
|
||||
tileIdxs: Array<number>,
|
||||
pieceIdxs: Array<number>,
|
||||
owner: string|number
|
||||
): void => {
|
||||
for (let tileIdx of tileIdxs) {
|
||||
changeTile(gameId, tileIdx, { owner })
|
||||
for (const pieceIdx of pieceIdxs) {
|
||||
changePiece(gameId, pieceIdx, { owner })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -564,7 +429,7 @@ function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] {
|
|||
|
||||
const grouped = []
|
||||
if (piece.group) {
|
||||
for (let other of pieces) {
|
||||
for (const other of pieces) {
|
||||
const otherPiece = Util.decodePiece(other)
|
||||
if (otherPiece.group === piece.group) {
|
||||
grouped.push(otherPiece.idx)
|
||||
|
|
@ -579,8 +444,8 @@ function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] {
|
|||
// Returns the index of the puzzle tile with the highest z index
|
||||
// that is not finished yet and that matches the position
|
||||
const freePieceIdxByPos = (gameId: string, pos: Point): number => {
|
||||
let info = GAMES[gameId].puzzle.info
|
||||
let pieces = GAMES[gameId].puzzle.tiles
|
||||
const info = GAMES[gameId].puzzle.info
|
||||
const pieces = GAMES[gameId].puzzle.tiles
|
||||
|
||||
let maxZ = -1
|
||||
let pieceIdx = -1
|
||||
|
|
@ -664,28 +529,28 @@ const getPuzzleHeight = (gameId: string): number => {
|
|||
function handleInput(
|
||||
gameId: string,
|
||||
playerId: string,
|
||||
input: any,
|
||||
ts: number
|
||||
): Array<Array<any>> {
|
||||
input: Input,
|
||||
ts: Timestamp
|
||||
): Array<Change> {
|
||||
const puzzle = GAMES[gameId].puzzle
|
||||
const evtInfo = getEvtInfo(gameId, playerId)
|
||||
|
||||
const changes = [] as Array<Array<any>>
|
||||
const changes: Array<Change> = []
|
||||
|
||||
const _dataChange = (): void => {
|
||||
changes.push([Protocol.CHANGE_DATA, puzzle.data])
|
||||
}
|
||||
|
||||
const _tileChange = (tileIdx: number): void => {
|
||||
const _pieceChange = (pieceIdx: number): void => {
|
||||
changes.push([
|
||||
Protocol.CHANGE_TILE,
|
||||
Util.encodePiece(getPiece(gameId, tileIdx)),
|
||||
Util.encodePiece(getPiece(gameId, pieceIdx)),
|
||||
])
|
||||
}
|
||||
|
||||
const _tileChanges = (tileIdxs: Array<number>): void => {
|
||||
for (const tileIdx of tileIdxs) {
|
||||
_tileChange(tileIdx)
|
||||
const _pieceChanges = (pieceIdxs: Array<number>): void => {
|
||||
for (const pieceIdx of pieceIdxs) {
|
||||
_pieceChange(pieceIdx)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -703,12 +568,12 @@ function handleInput(
|
|||
// put both tiles (and their grouped tiles) in the same group
|
||||
const groupTiles = (
|
||||
gameId: string,
|
||||
tileIdx1: number,
|
||||
tileIdx2: number
|
||||
pieceIdx1: number,
|
||||
pieceIdx2: number
|
||||
): void => {
|
||||
const tiles = GAMES[gameId].puzzle.tiles
|
||||
const group1 = getPieceGroup(gameId, tileIdx1)
|
||||
const group2 = getPieceGroup(gameId, tileIdx2)
|
||||
const pieces = GAMES[gameId].puzzle.tiles
|
||||
const group1 = getPieceGroup(gameId, pieceIdx1)
|
||||
const group2 = getPieceGroup(gameId, pieceIdx2)
|
||||
|
||||
let group
|
||||
const searchGroups = []
|
||||
|
|
@ -729,18 +594,18 @@ function handleInput(
|
|||
group = getMaxGroup(gameId)
|
||||
}
|
||||
|
||||
changeTile(gameId, tileIdx1, { group })
|
||||
_tileChange(tileIdx1)
|
||||
changeTile(gameId, tileIdx2, { group })
|
||||
_tileChange(tileIdx2)
|
||||
changePiece(gameId, pieceIdx1, { group })
|
||||
_pieceChange(pieceIdx1)
|
||||
changePiece(gameId, pieceIdx2, { group })
|
||||
_pieceChange(pieceIdx2)
|
||||
|
||||
// TODO: strange
|
||||
if (searchGroups.length > 0) {
|
||||
for (const t of tiles) {
|
||||
const piece = Util.decodePiece(t)
|
||||
for (const p of pieces) {
|
||||
const piece = Util.decodePiece(p)
|
||||
if (searchGroups.includes(piece.group)) {
|
||||
changeTile(gameId, piece.idx, { group })
|
||||
_tileChange(piece.idx)
|
||||
changePiece(gameId, piece.idx, { group })
|
||||
_pieceChange(piece.idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -770,13 +635,13 @@ function handleInput(
|
|||
|
||||
const tileIdxAtPos = freePieceIdxByPos(gameId, pos)
|
||||
if (tileIdxAtPos >= 0) {
|
||||
let maxZ = getMaxZIndex(gameId) + 1
|
||||
const maxZ = getMaxZIndex(gameId) + 1
|
||||
changeData(gameId, { maxZ })
|
||||
_dataChange()
|
||||
const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos)
|
||||
setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId))
|
||||
setPiecesZIndex(gameId, tileIdxs, getMaxZIndex(gameId))
|
||||
setTilesOwner(gameId, tileIdxs, playerId)
|
||||
_tileChanges(tileIdxs)
|
||||
_pieceChanges(tileIdxs)
|
||||
}
|
||||
evtInfo._last_mouse = pos
|
||||
|
||||
|
|
@ -790,18 +655,18 @@ function handleInput(
|
|||
changePlayer(gameId, playerId, {x, y, ts})
|
||||
_playerChange()
|
||||
} else {
|
||||
let tileIdx = getFirstOwnedPieceIdx(gameId, playerId)
|
||||
if (tileIdx >= 0) {
|
||||
const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId)
|
||||
if (pieceIdx >= 0) {
|
||||
// player is moving a tile (and hand)
|
||||
changePlayer(gameId, playerId, {x, y, ts})
|
||||
_playerChange()
|
||||
|
||||
// check if pos is on the tile, otherwise dont move
|
||||
// (mouse could be out of table, but tile stays on it)
|
||||
const tileIdxs = getGroupedPieceIdxs(gameId, tileIdx)
|
||||
const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx)
|
||||
let anyOk = Geometry.pointInBounds(pos, getBounds(gameId))
|
||||
&& Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId))
|
||||
for (let idx of tileIdxs) {
|
||||
for (const idx of pieceIdxs) {
|
||||
const bounds = getPieceBounds(gameId, idx)
|
||||
if (Geometry.pointInBounds(pos, bounds)) {
|
||||
anyOk = true
|
||||
|
|
@ -813,9 +678,9 @@ function handleInput(
|
|||
const diffY = y - evtInfo._last_mouse_down.y
|
||||
|
||||
const diff = { x: diffX, y: diffY }
|
||||
moveTilesDiff(gameId, tileIdxs, diff)
|
||||
movePiecesDiff(gameId, pieceIdxs, diff)
|
||||
|
||||
_tileChanges(tileIdxs)
|
||||
_pieceChanges(pieceIdxs)
|
||||
}
|
||||
} else {
|
||||
// player is just moving map, so no change in position!
|
||||
|
|
@ -835,26 +700,26 @@ function handleInput(
|
|||
|
||||
evtInfo._last_mouse_down = null
|
||||
|
||||
let tileIdx = getFirstOwnedPieceIdx(gameId, playerId)
|
||||
if (tileIdx >= 0) {
|
||||
const pieceIdx = getFirstOwnedPieceIdx(gameId, playerId)
|
||||
if (pieceIdx >= 0) {
|
||||
// drop the tile(s)
|
||||
let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx)
|
||||
setTilesOwner(gameId, tileIdxs, 0)
|
||||
_tileChanges(tileIdxs)
|
||||
const pieceIdxs = getGroupedPieceIdxs(gameId, pieceIdx)
|
||||
setTilesOwner(gameId, pieceIdxs, 0)
|
||||
_pieceChanges(pieceIdxs)
|
||||
|
||||
// Check if the tile was dropped near the final location
|
||||
let tilePos = getPiecePos(gameId, tileIdx)
|
||||
let finalPos = getFinalPiecePos(gameId, tileIdx)
|
||||
const tilePos = getPiecePos(gameId, pieceIdx)
|
||||
const finalPos = getFinalPiecePos(gameId, pieceIdx)
|
||||
if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) {
|
||||
let diff = Geometry.pointSub(finalPos, tilePos)
|
||||
const diff = Geometry.pointSub(finalPos, tilePos)
|
||||
// Snap the tile to the final destination
|
||||
moveTilesDiff(gameId, tileIdxs, diff)
|
||||
finishTiles(gameId, tileIdxs)
|
||||
_tileChanges(tileIdxs)
|
||||
movePiecesDiff(gameId, pieceIdxs, diff)
|
||||
finishPieces(gameId, pieceIdxs)
|
||||
_pieceChanges(pieceIdxs)
|
||||
|
||||
let points = getPlayerPoints(gameId, playerId)
|
||||
if (getScoreMode(gameId) === ScoreMode.FINAL) {
|
||||
points += tileIdxs.length
|
||||
points += pieceIdxs.length
|
||||
} else if (getScoreMode(gameId) === ScoreMode.ANY) {
|
||||
points += 1
|
||||
} else {
|
||||
|
|
@ -877,7 +742,7 @@ function handleInput(
|
|||
otherTileIdx: number,
|
||||
off: Array<number>
|
||||
): boolean => {
|
||||
let info = GAMES[gameId].puzzle.info
|
||||
const info = GAMES[gameId].puzzle.info
|
||||
if (otherTileIdx < 0) {
|
||||
return false
|
||||
}
|
||||
|
|
@ -890,27 +755,27 @@ function handleInput(
|
|||
{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 = getGroupedPieceIdxs(gameId, tileIdx)
|
||||
moveTilesDiff(gameId, tileIdxs, diff)
|
||||
const diff = Geometry.pointSub(dstPos, tilePos)
|
||||
let pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx)
|
||||
movePiecesDiff(gameId, pieceIdxs, diff)
|
||||
groupTiles(gameId, tileIdx, otherTileIdx)
|
||||
tileIdxs = getGroupedPieceIdxs(gameId, tileIdx)
|
||||
const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs)
|
||||
setTilesZIndex(gameId, tileIdxs, zIndex)
|
||||
_tileChanges(tileIdxs)
|
||||
pieceIdxs = getGroupedPieceIdxs(gameId, tileIdx)
|
||||
const zIndex = getMaxZIndexByPieceIdxs(gameId, pieceIdxs)
|
||||
setPiecesZIndex(gameId, pieceIdxs, zIndex)
|
||||
_pieceChanges(pieceIdxs)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let snapped = false
|
||||
for (let tileIdxTmp of getGroupedPieceIdxs(gameId, tileIdx)) {
|
||||
let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp)
|
||||
for (const pieceIdxTmp of getGroupedPieceIdxs(gameId, pieceIdx)) {
|
||||
const othersIdxs = getSurroundingTilesByIdx(gameId, pieceIdxTmp)
|
||||
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
|
||||
check(gameId, pieceIdxTmp, othersIdxs[0], [0, 1]) // top
|
||||
|| check(gameId, pieceIdxTmp, othersIdxs[1], [-1, 0]) // right
|
||||
|| check(gameId, pieceIdxTmp, othersIdxs[2], [0, -1]) // bottom
|
||||
|| check(gameId, pieceIdxTmp, othersIdxs[3], [1, 0]) // left
|
||||
) {
|
||||
snapped = true
|
||||
break
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export class Rng {
|
|||
random (min: number, max: number): number {
|
||||
this.rand_high = ((this.rand_high << 16) + (this.rand_high >> 16) + this.rand_low) & 0xffffffff;
|
||||
this.rand_low = (this.rand_low + this.rand_high) & 0xffffffff;
|
||||
var n = (this.rand_high >>> 0) / 0xffffffff;
|
||||
const n = (this.rand_high >>> 0) / 0xffffffff;
|
||||
return (min + n * (max-min+1))|0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,199 @@
|
|||
import { Point } from "./Geometry"
|
||||
import { Rng, RngSerialized } from "./Rng"
|
||||
|
||||
// @see https://stackoverflow.com/a/59906630/392905
|
||||
type ArrayLengthMutationKeys = 'splice' | 'push' | 'pop' | 'shift' | 'unshift' | number
|
||||
type ArrayItems<T extends Array<any>> = T extends Array<infer TItems> ? TItems : never
|
||||
export type FixedLengthArray<T extends any[]> =
|
||||
Pick<T, Exclude<keyof T, ArrayLengthMutationKeys>>
|
||||
& { [Symbol.iterator]: () => IterableIterator< ArrayItems<T> > }
|
||||
|
||||
export type Timestamp = number
|
||||
|
||||
export type Input = any
|
||||
export type Change = Array<any>
|
||||
|
||||
export type GameEvent = Array<any>
|
||||
|
||||
export type ClientEvent = Array<any>
|
||||
|
||||
export type EncodedPlayer = FixedLengthArray<[
|
||||
string,
|
||||
number,
|
||||
number,
|
||||
0|1,
|
||||
string|null,
|
||||
string|null,
|
||||
string|null,
|
||||
number,
|
||||
Timestamp,
|
||||
]>
|
||||
|
||||
export type EncodedPiece = FixedLengthArray<[
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
string|number,
|
||||
number,
|
||||
]>
|
||||
|
||||
export type EncodedPieceShape = number
|
||||
|
||||
export type EncodedGame = FixedLengthArray<[
|
||||
string,
|
||||
string,
|
||||
RngSerialized,
|
||||
Puzzle,
|
||||
Array<EncodedPlayer>,
|
||||
Record<string, EvtInfo>,
|
||||
ScoreMode,
|
||||
]>
|
||||
|
||||
export interface ReplayData {
|
||||
log: any[],
|
||||
game: EncodedGame|null
|
||||
}
|
||||
|
||||
export interface Tag {
|
||||
id: number
|
||||
slug: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface GameRng {
|
||||
obj: Rng
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface Game {
|
||||
id: string
|
||||
players: Array<EncodedPlayer>
|
||||
puzzle: Puzzle
|
||||
evtInfos: Record<string, EvtInfo>
|
||||
scoreMode?: ScoreMode
|
||||
rng: GameRng
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
id: number
|
||||
filename: string
|
||||
file: string
|
||||
url: string
|
||||
title: string
|
||||
tags: Array<Tag>
|
||||
created: number
|
||||
}
|
||||
|
||||
export interface GameSettings {
|
||||
tiles: number
|
||||
image: Image
|
||||
scoreMode: ScoreMode
|
||||
}
|
||||
|
||||
export interface Puzzle {
|
||||
tiles: Array<EncodedPiece>
|
||||
data: PuzzleData
|
||||
info: PuzzleInfo
|
||||
}
|
||||
|
||||
export interface PuzzleData {
|
||||
started: number
|
||||
finished: number
|
||||
maxGroup: number
|
||||
maxZ: number
|
||||
}
|
||||
|
||||
export interface PuzzleDataChange {
|
||||
started?: number
|
||||
finished?: number
|
||||
maxGroup?: number
|
||||
maxZ?: number
|
||||
}
|
||||
|
||||
interface PuzzleTable {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
enum PieceEdge {
|
||||
Flat = 0,
|
||||
Out = 1,
|
||||
In = -1,
|
||||
}
|
||||
export interface PieceShape {
|
||||
top: PieceEdge
|
||||
bottom: PieceEdge
|
||||
left: PieceEdge
|
||||
right: PieceEdge
|
||||
}
|
||||
|
||||
export interface Piece {
|
||||
owner: string|number
|
||||
idx: number
|
||||
pos: Point
|
||||
z: number
|
||||
group: number
|
||||
}
|
||||
|
||||
export interface PieceChange {
|
||||
owner?: string|number
|
||||
idx?: number
|
||||
pos?: Point
|
||||
z?: number
|
||||
group?: number
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
shapes: Array<EncodedPieceShape>
|
||||
}
|
||||
|
||||
export interface Player {
|
||||
id: string
|
||||
x: number
|
||||
y: number
|
||||
d: 0|1
|
||||
name: string|null
|
||||
color: string|null
|
||||
bgcolor: string|null
|
||||
points: number
|
||||
ts: Timestamp
|
||||
}
|
||||
|
||||
export interface PlayerChange {
|
||||
id?: string
|
||||
x?: number
|
||||
y?: number
|
||||
d?: 0|1
|
||||
name?: string|null
|
||||
color?: string|null
|
||||
bgcolor?: string|null
|
||||
points?: number
|
||||
ts?: Timestamp
|
||||
}
|
||||
|
||||
export interface EvtInfo {
|
||||
_last_mouse: Point|null
|
||||
_last_mouse_down: Point|null
|
||||
}
|
||||
|
||||
export enum ScoreMode {
|
||||
FINAL = 0,
|
||||
ANY = 1,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { PuzzleCreationInfo } from '../server/Puzzle'
|
||||
import {
|
||||
EncodedGame,
|
||||
EncodedPiece,
|
||||
|
|
@ -7,19 +8,20 @@ import {
|
|||
Piece,
|
||||
PieceShape,
|
||||
Player,
|
||||
PuzzleInfo,
|
||||
ScoreMode
|
||||
} from './GameCommon'
|
||||
} from './Types'
|
||||
import { Point } from './Geometry'
|
||||
import { Rng } from './Rng'
|
||||
|
||||
const slug = (str: string) => {
|
||||
const slug = (str: string): string => {
|
||||
let tmp = str.toLowerCase()
|
||||
tmp = tmp.replace(/[^a-z0-9]+/g, '-')
|
||||
tmp = tmp.replace(/^-|-$/, '')
|
||||
return tmp
|
||||
}
|
||||
|
||||
const pad = (x: any, pad: string) => {
|
||||
const pad = (x: number, pad: string): string => {
|
||||
const str = `${x}`
|
||||
if (str.length >= pad.length) {
|
||||
return str
|
||||
|
|
@ -43,7 +45,9 @@ export const logger = (...pre: Array<any>) => {
|
|||
}
|
||||
|
||||
// get a unique id
|
||||
export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2)
|
||||
export const uniqId = (): string => {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substring(2)
|
||||
}
|
||||
|
||||
function encodeShape(data: PieceShape): EncodedPieceShape {
|
||||
/* encoded in 1 byte:
|
||||
|
|
@ -139,11 +143,11 @@ function decodeGame(data: EncodedGame): Game {
|
|||
}
|
||||
}
|
||||
|
||||
function coordByTileIdx(info: any, tileIdx: number): Point {
|
||||
function coordByPieceIdx(info: PuzzleInfo|PuzzleCreationInfo, pieceIdx: number): Point {
|
||||
const wTiles = info.width / info.tileSize
|
||||
return {
|
||||
x: tileIdx % wTiles,
|
||||
y: Math.floor(tileIdx / wTiles),
|
||||
x: pieceIdx % wTiles,
|
||||
y: Math.floor(pieceIdx / wTiles),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,16 +155,16 @@ const hash = (str: string): number => {
|
|||
let hash = 0
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let chr = str.charCodeAt(i);
|
||||
const chr = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
function asQueryArgs(data: any) {
|
||||
function asQueryArgs(data: Record<string, any>): string {
|
||||
const q = []
|
||||
for (let k in data) {
|
||||
for (const k in data) {
|
||||
const pair = [k, data[k]].map(encodeURIComponent)
|
||||
q.push(pair.join('='))
|
||||
}
|
||||
|
|
@ -187,7 +191,7 @@ export default {
|
|||
encodeGame,
|
||||
decodeGame,
|
||||
|
||||
coordByTileIdx,
|
||||
coordByPieceIdx,
|
||||
|
||||
asQueryArgs,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"use strict"
|
||||
|
||||
import { EncodedGame, ReplayData } from '../common/GameCommon'
|
||||
import { ClientEvent, EncodedGame, GameEvent, ReplayData } from '../common/Types'
|
||||
import Util, { logger } from '../common/Util'
|
||||
import Protocol from './../common/Protocol'
|
||||
|
||||
|
|
@ -16,25 +16,29 @@ const CONN_STATE_CONNECTING = 3 // connecting
|
|||
const CONN_STATE_CLOSED = 4 // not connected (closed on purpose)
|
||||
|
||||
let ws: WebSocket
|
||||
let changesCallback = (msg: Array<any>) => {}
|
||||
let connectionStateChangeCallback = (state: number) => {}
|
||||
let changesCallback = (msg: Array<any>) => {
|
||||
// empty
|
||||
}
|
||||
let connectionStateChangeCallback = (state: number) => {
|
||||
// empty
|
||||
}
|
||||
|
||||
// TODO: change these to something like on(EVT, cb)
|
||||
function onServerChange(callback: (msg: Array<any>) => void) {
|
||||
function onServerChange(callback: (msg: Array<any>) => void): void {
|
||||
changesCallback = callback
|
||||
}
|
||||
function onConnectionStateChange(callback: (state: number) => void) {
|
||||
function onConnectionStateChange(callback: (state: number) => void): void {
|
||||
connectionStateChangeCallback = callback
|
||||
}
|
||||
|
||||
let connectionState = CONN_STATE_NOT_CONNECTED
|
||||
const setConnectionState = (state: number) => {
|
||||
const setConnectionState = (state: number): void => {
|
||||
if (connectionState !== state) {
|
||||
connectionState = state
|
||||
connectionStateChangeCallback(state)
|
||||
}
|
||||
}
|
||||
function send(message: Array<any>): void {
|
||||
function send(message: ClientEvent): void {
|
||||
if (connectionState === CONN_STATE_CONNECTED) {
|
||||
try {
|
||||
ws.send(JSON.stringify(message))
|
||||
|
|
@ -46,7 +50,7 @@ function send(message: Array<any>): void {
|
|||
|
||||
|
||||
let clientSeq: number
|
||||
let events: Record<number, any>
|
||||
let events: Record<number, GameEvent>
|
||||
|
||||
function connect(
|
||||
address: string,
|
||||
|
|
@ -58,7 +62,7 @@ function connect(
|
|||
setConnectionState(CONN_STATE_CONNECTING)
|
||||
return new Promise(resolve => {
|
||||
ws = new WebSocket(address, clientId + '|' + gameId)
|
||||
ws.onopen = (e) => {
|
||||
ws.onopen = () => {
|
||||
setConnectionState(CONN_STATE_CONNECTED)
|
||||
send([Protocol.EV_CLIENT_INIT])
|
||||
}
|
||||
|
|
@ -82,7 +86,7 @@ function connect(
|
|||
}
|
||||
}
|
||||
|
||||
ws.onerror = (e) => {
|
||||
ws.onerror = () => {
|
||||
setConnectionState(CONN_STATE_DISCONNECTED)
|
||||
throw `[ 2021-05-15 onerror ]`
|
||||
}
|
||||
|
|
@ -116,7 +120,7 @@ function disconnect(): void {
|
|||
events = {}
|
||||
}
|
||||
|
||||
function sendClientEvent(evt: any): void {
|
||||
function sendClientEvent(evt: GameEvent): void {
|
||||
// when sending event, increase number of sent events
|
||||
// and add the event locally
|
||||
clientSeq++;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ const log = logger('Debug.js')
|
|||
let _pt = 0
|
||||
let _mindiff = 0
|
||||
|
||||
const checkpoint_start = (mindiff: number) => {
|
||||
const checkpoint_start = (mindiff: number): void => {
|
||||
_pt = performance.now()
|
||||
_mindiff = mindiff
|
||||
}
|
||||
|
||||
const checkpoint = (label: string) => {
|
||||
const checkpoint = (label: string): void => {
|
||||
const now = performance.now()
|
||||
const diff = now - _pt
|
||||
if (diff > _mindiff) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"use strict"
|
||||
|
||||
import { Rng } from '../common/Rng'
|
||||
import Util from '../common/Util'
|
||||
|
||||
let minVx = -10
|
||||
let deltaVx = 20
|
||||
|
|
@ -108,11 +107,11 @@ class Bomb {
|
|||
}
|
||||
|
||||
class Particle {
|
||||
px: any
|
||||
py: any
|
||||
px: number
|
||||
py: number
|
||||
vx: number
|
||||
vy: number
|
||||
color: any
|
||||
color: string
|
||||
duration: number
|
||||
alive: boolean
|
||||
radius: number
|
||||
|
|
@ -171,7 +170,7 @@ class Controller {
|
|||
})
|
||||
}
|
||||
|
||||
setSpeedParams() {
|
||||
setSpeedParams(): void {
|
||||
let heightReached = 0
|
||||
let vy = 0
|
||||
|
||||
|
|
@ -188,11 +187,11 @@ class Controller {
|
|||
deltaVx = 2 * vx
|
||||
}
|
||||
|
||||
resize() {
|
||||
resize(): void {
|
||||
this.setSpeedParams()
|
||||
}
|
||||
|
||||
init() {
|
||||
init(): void {
|
||||
this.readyBombs = []
|
||||
this.explodedBombs = []
|
||||
this.particles = []
|
||||
|
|
@ -202,7 +201,7 @@ class Controller {
|
|||
}
|
||||
}
|
||||
|
||||
update() {
|
||||
update(): void {
|
||||
if (Math.random() * 100 < percentChanceNewBomb) {
|
||||
this.readyBombs.push(new Bomb(this.rng))
|
||||
}
|
||||
|
|
@ -250,7 +249,7 @@ class Controller {
|
|||
this.particles = aliveParticles
|
||||
}
|
||||
|
||||
render() {
|
||||
render(): void {
|
||||
this.ctx.beginPath()
|
||||
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' // Ghostly effect
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
|
|
|
|||
|
|
@ -3,22 +3,22 @@
|
|||
import Geometry, { Rect } from '../common/Geometry'
|
||||
import Graphics from './Graphics'
|
||||
import Util, { logger } from './../common/Util'
|
||||
import { Puzzle, PuzzleInfo, PieceShape } from './../common/GameCommon'
|
||||
import { Puzzle, PuzzleInfo, PieceShape, EncodedPiece } from './../common/GameCommon'
|
||||
|
||||
const log = logger('PuzzleGraphics.js')
|
||||
|
||||
async function createPuzzleTileBitmaps(
|
||||
img: ImageBitmap,
|
||||
tiles: Array<any>,
|
||||
pieces: EncodedPiece[],
|
||||
info: PuzzleInfo
|
||||
): Promise<Array<ImageBitmap>> {
|
||||
log.log('start createPuzzleTileBitmaps')
|
||||
var tileSize = info.tileSize
|
||||
var tileMarginWidth = info.tileMarginWidth
|
||||
var tileDrawSize = info.tileDrawSize
|
||||
var tileRatio = tileSize / 100.0
|
||||
const tileSize = info.tileSize
|
||||
const tileMarginWidth = info.tileMarginWidth
|
||||
const tileDrawSize = info.tileDrawSize
|
||||
const tileRatio = tileSize / 100.0
|
||||
|
||||
var curvyCoords = [
|
||||
const curvyCoords = [
|
||||
0, 0, 40, 15, 37, 5,
|
||||
37, 5, 40, 0, 38, -5,
|
||||
38, -5, 20, -20, 50, -20,
|
||||
|
|
@ -27,7 +27,7 @@ async function createPuzzleTileBitmaps(
|
|||
63, 5, 65, 15, 100, 0
|
||||
];
|
||||
|
||||
const bitmaps: Array<ImageBitmap> = new Array(tiles.length)
|
||||
const bitmaps: Array<ImageBitmap> = new Array(pieces.length)
|
||||
|
||||
const paths: Record<string, Path2D> = {}
|
||||
function pathForShape(shape: PieceShape) {
|
||||
|
|
@ -65,9 +65,9 @@ async function createPuzzleTileBitmaps(
|
|||
}
|
||||
if (shape.bottom !== 0) {
|
||||
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 })
|
||||
const p1 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 0] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 1] * tileRatio })
|
||||
const p2 = Geometry.pointSub(bottomRightEdge, { x: curvyCoords[i * 6 + 2] * tileRatio, y: shape.bottom * curvyCoords[i * 6 + 3] * tileRatio })
|
||||
const 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);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -75,9 +75,9 @@ async function createPuzzleTileBitmaps(
|
|||
}
|
||||
if (shape.left !== 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 })
|
||||
const p1 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 1] * tileRatio, y: curvyCoords[i * 6 + 0] * tileRatio })
|
||||
const p2 = Geometry.pointSub(bottomLeftEdge, { x: -shape.left * curvyCoords[i * 6 + 3] * tileRatio, y: curvyCoords[i * 6 + 2] * tileRatio })
|
||||
const 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);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -93,10 +93,10 @@ async function createPuzzleTileBitmaps(
|
|||
const c2 = Graphics.createCanvas(tileDrawSize, tileDrawSize)
|
||||
const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D
|
||||
|
||||
for (const t of tiles) {
|
||||
const tile = Util.decodePiece(t)
|
||||
const srcRect = srcRectByIdx(info, tile.idx)
|
||||
const path = pathForShape(Util.decodeShape(info.shapes[tile.idx]))
|
||||
for (const p of pieces) {
|
||||
const piece = Util.decodePiece(p)
|
||||
const srcRect = srcRectByIdx(info, piece.idx)
|
||||
const path = pathForShape(Util.decodeShape(info.shapes[piece.idx]))
|
||||
|
||||
ctx.clearRect(0, 0, tileDrawSize, tileDrawSize)
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ async function createPuzzleTileBitmaps(
|
|||
ctx2.restore()
|
||||
ctx.drawImage(c2, 0, 0)
|
||||
|
||||
bitmaps[tile.idx] = await createImageBitmap(c)
|
||||
bitmaps[piece.idx] = await createImageBitmap(c)
|
||||
}
|
||||
|
||||
log.log('end createPuzzleTileBitmaps')
|
||||
|
|
@ -203,7 +203,7 @@ async function createPuzzleTileBitmaps(
|
|||
}
|
||||
|
||||
function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number): Rect {
|
||||
const c = Util.coordByTileIdx(puzzleInfo, idx)
|
||||
const c = Util.coordByPieceIdx(puzzleInfo, idx)
|
||||
return {
|
||||
x: c.x * puzzleInfo.tileSize,
|
||||
y: c.y * puzzleInfo.tileSize,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
import { GameSettings, ScoreMode } from './../../common/GameCommon'
|
||||
import { GameSettings, ScoreMode } from './../../common/Types'
|
||||
import ResponsiveImage from './ResponsiveImage.vue'
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
|||
|
|
@ -7,13 +7,21 @@ import Debug from './Debug'
|
|||
import Communication from './Communication'
|
||||
import Util from './../common/Util'
|
||||
import PuzzleGraphics from './PuzzleGraphics'
|
||||
import Game, { Game as GameType, Player, Piece, EncodedGame, ReplayData, Timestamp } from './../common/GameCommon'
|
||||
import Game from './../common/GameCommon'
|
||||
import fireworksController from './Fireworks'
|
||||
import Protocol from '../common/Protocol'
|
||||
import Time from '../common/Time'
|
||||
import { Dim, Point } from '../common/Geometry'
|
||||
import { FixedLengthArray } from '../common/Types'
|
||||
|
||||
import {
|
||||
FixedLengthArray,
|
||||
Game as GameType,
|
||||
Player,
|
||||
Piece,
|
||||
EncodedGame,
|
||||
ReplayData,
|
||||
Timestamp,
|
||||
GameEvent,
|
||||
} from '../common/Types'
|
||||
declare global {
|
||||
interface Window {
|
||||
DEBUG?: boolean
|
||||
|
|
@ -79,7 +87,7 @@ function addCanvasToDom(TARGET_EL: HTMLElement, canvas: HTMLCanvasElement) {
|
|||
}
|
||||
|
||||
function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) {
|
||||
let events: Array<Array<any>> = []
|
||||
let events: Array<GameEvent> = []
|
||||
|
||||
let KEYS_ON = true
|
||||
|
||||
|
|
@ -165,11 +173,11 @@ function EventAdapter (canvas: HTMLCanvasElement, window: any, viewport: any) {
|
|||
}
|
||||
})
|
||||
|
||||
const addEvent = (event: Array<any>) => {
|
||||
const addEvent = (event: GameEvent) => {
|
||||
events.push(event)
|
||||
}
|
||||
|
||||
const consumeAll = () => {
|
||||
const consumeAll = (): GameEvent[] => {
|
||||
if (events.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
|
@ -491,7 +499,7 @@ export async function main(
|
|||
} else if (MODE === MODE_REPLAY) {
|
||||
// no external communication for replay mode,
|
||||
// only the REPLAY.log is relevant
|
||||
let inter = setInterval(() => {
|
||||
const inter = setInterval(() => {
|
||||
const realTs = Time.timestamp()
|
||||
if (REPLAY.requesting) {
|
||||
REPLAY.lastRealTs = realTs
|
||||
|
|
@ -559,7 +567,7 @@ export async function main(
|
|||
}
|
||||
|
||||
let _last_mouse_down: Point|null = null
|
||||
const onUpdate = () => {
|
||||
const onUpdate = (): void => {
|
||||
// handle key downs once per onUpdate
|
||||
// this will create Protocol.INPUT_EV_MOVE events if something
|
||||
// relevant is pressed
|
||||
|
|
@ -663,7 +671,7 @@ export async function main(
|
|||
}
|
||||
}
|
||||
|
||||
const onRender = async () => {
|
||||
const onRender = async (): Promise<void> => {
|
||||
if (!RERENDER) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
interface GameLoopOptions {
|
||||
fps?: number
|
||||
slow?: number
|
||||
update: (step: number) => any
|
||||
render: (passed: number) => any
|
||||
update: (step: number) => void
|
||||
render: (passed: number) => void
|
||||
}
|
||||
export const run = (options: GameLoopOptions) => {
|
||||
export const run = (options: GameLoopOptions): void => {
|
||||
const fps = options.fps || 60
|
||||
const slow = options.slow || 1
|
||||
const update = options.update
|
||||
|
|
|
|||
|
|
@ -13,11 +13,12 @@ const log = logger('Db.ts')
|
|||
* {name: 1}, // then by name ascending
|
||||
* ]
|
||||
*/
|
||||
type OrderBy = Array<any>
|
||||
type Data = Record<string, any>
|
||||
type WhereRaw = Record<string, any>
|
||||
type Params = Array<any>
|
||||
|
||||
export type WhereRaw = Record<string, any>
|
||||
export type OrderBy = Array<Record<string, 1|-1>>
|
||||
|
||||
interface Where {
|
||||
sql: string
|
||||
values: Array<any>
|
||||
|
|
@ -88,7 +89,7 @@ class Db {
|
|||
let prop = '$nin'
|
||||
if (where[k][prop]) {
|
||||
if (where[k][prop].length > 0) {
|
||||
wheres.push(k + ' NOT IN (' + where[k][prop].map((_: any) => '?') + ')')
|
||||
wheres.push(k + ' NOT IN (' + where[k][prop].map(() => '?') + ')')
|
||||
values.push(...where[k][prop])
|
||||
}
|
||||
continue
|
||||
|
|
@ -96,7 +97,7 @@ class Db {
|
|||
prop = '$in'
|
||||
if (where[k][prop]) {
|
||||
if (where[k][prop].length > 0) {
|
||||
wheres.push(k + ' IN (' + where[k][prop].map((_: any) => '?') + ')')
|
||||
wheres.push(k + ' IN (' + where[k][prop].map(() => '?') + ')')
|
||||
values.push(...where[k][prop])
|
||||
}
|
||||
continue
|
||||
|
|
@ -191,7 +192,7 @@ class Db {
|
|||
const values = keys.map(k => data[k])
|
||||
const sql = 'INSERT INTO '+ table
|
||||
+ ' (' + keys.join(',') + ')'
|
||||
+ ' VALUES (' + keys.map(k => '?').join(',') + ')'
|
||||
+ ' VALUES (' + keys.map(() => '?').join(',') + ')'
|
||||
return this.run(sql, values).lastInsertRowid
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import GameCommon, { Game, ScoreMode, Timestamp } from './../common/GameCommon'
|
||||
import GameCommon from './../common/GameCommon'
|
||||
import { Change, Game, Input, ScoreMode, Timestamp } from './../common/Types'
|
||||
import Util from './../common/Util'
|
||||
import { Rng } from './../common/Rng'
|
||||
import GameLog from './GameLog'
|
||||
|
|
@ -10,7 +11,7 @@ async function createGameObject(
|
|||
gameId: string,
|
||||
targetTiles: number,
|
||||
image: PuzzleCreationImageInfo,
|
||||
ts: number,
|
||||
ts: Timestamp,
|
||||
scoreMode: ScoreMode
|
||||
): Promise<Game> {
|
||||
const seed = Util.hash(gameId + ' ' + ts)
|
||||
|
|
@ -29,7 +30,7 @@ async function createGame(
|
|||
gameId: string,
|
||||
targetTiles: number,
|
||||
image: PuzzleCreationImageInfo,
|
||||
ts: number,
|
||||
ts: Timestamp,
|
||||
scoreMode: ScoreMode
|
||||
): Promise<void> {
|
||||
const gameObject = await createGameObject(
|
||||
|
|
@ -63,9 +64,9 @@ function addPlayer(gameId: string, playerId: string, ts: Timestamp): void {
|
|||
function handleInput(
|
||||
gameId: string,
|
||||
playerId: string,
|
||||
input: any,
|
||||
ts: number
|
||||
): Array<Array<any>> {
|
||||
input: Input,
|
||||
ts: Timestamp
|
||||
): Array<Change> {
|
||||
const idx = GameCommon.getPlayerIndexById(gameId, playerId)
|
||||
const diff = ts - GameCommon.getStartTs(gameId)
|
||||
GameLog.log(gameId, Protocol.LOG_HANDLE_INPUT, idx, input, diff)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import fs from 'fs'
|
||||
import GameCommon, { Piece, ScoreMode } from './../common/GameCommon'
|
||||
import GameCommon from './../common/GameCommon'
|
||||
import { Piece, ScoreMode } from './../common/Types'
|
||||
import Util, { logger } from './../common/Util'
|
||||
import { Rng } from './../common/Rng'
|
||||
import { DATA_DIR } from './Dirs'
|
||||
|
|
@ -7,12 +8,12 @@ import Time from './../common/Time'
|
|||
|
||||
const log = logger('GameStorage.js')
|
||||
|
||||
const DIRTY_GAMES = {} as any
|
||||
const dirtyGames: Record<string, boolean> = {}
|
||||
function setDirty(gameId: string): void {
|
||||
DIRTY_GAMES[gameId] = true
|
||||
dirtyGames[gameId] = true
|
||||
}
|
||||
function setClean(gameId: string): void {
|
||||
delete DIRTY_GAMES[gameId]
|
||||
delete dirtyGames[gameId]
|
||||
}
|
||||
|
||||
function loadGames(): void {
|
||||
|
|
@ -63,14 +64,19 @@ function loadGame(gameId: string): void {
|
|||
}
|
||||
|
||||
function persistGames(): void {
|
||||
for (const gameId of Object.keys(DIRTY_GAMES)) {
|
||||
for (const gameId of Object.keys(dirtyGames)) {
|
||||
persistGame(gameId)
|
||||
}
|
||||
}
|
||||
|
||||
function persistGame(gameId: string): void {
|
||||
const game = GameCommon.get(gameId)
|
||||
if (game.id in DIRTY_GAMES) {
|
||||
if (!game) {
|
||||
log.error(`[ERROR] unable to persist non existing game ${gameId}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (game.id in dirtyGames) {
|
||||
setClean(game.id)
|
||||
}
|
||||
fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({
|
||||
|
|
|
|||
|
|
@ -4,10 +4,32 @@ import exif from 'exif'
|
|||
import sharp from 'sharp'
|
||||
|
||||
import {UPLOAD_DIR, UPLOAD_URL} from './Dirs'
|
||||
import Db from './Db'
|
||||
import Db, { OrderBy, WhereRaw } from './Db'
|
||||
import { Dim } from '../common/Geometry'
|
||||
import { logger } from '../common/Util'
|
||||
import { Timestamp } from '../common/Types'
|
||||
|
||||
const resizeImage = async (filename: string) => {
|
||||
const log = logger('Images.ts')
|
||||
|
||||
interface Tag
|
||||
{
|
||||
id: number
|
||||
slug: string
|
||||
title: string
|
||||
}
|
||||
|
||||
interface ImageInfo
|
||||
{
|
||||
id: number
|
||||
filename: string
|
||||
file: string
|
||||
url: string
|
||||
title: string
|
||||
tags: Tag[]
|
||||
created: Timestamp,
|
||||
}
|
||||
|
||||
const resizeImage = async (filename: string): Promise<void> => {
|
||||
if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) {
|
||||
return
|
||||
}
|
||||
|
|
@ -30,33 +52,39 @@ const resizeImage = async (filename: string) => {
|
|||
[150, 100],
|
||||
[375, 210],
|
||||
]
|
||||
for (let [w,h] of sizes) {
|
||||
console.log(w, h, imagePath)
|
||||
await sharpImg.resize(w, h, { fit: 'contain' }).toFile(`${imageOutPath}-${w}x${h}.webp`)
|
||||
for (const [w,h] of sizes) {
|
||||
log.info(w, h, imagePath)
|
||||
await sharpImg
|
||||
.resize(w, h, { fit: 'contain' })
|
||||
.toFile(`${imageOutPath}-${w}x${h}.webp`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getExifOrientation(imagePath: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
new exif.ExifImage({ image: imagePath }, function (error, exifData) {
|
||||
async function getExifOrientation(imagePath: string): Promise<number> {
|
||||
return new Promise((resolve) => {
|
||||
new exif.ExifImage({ image: imagePath }, (error, exifData) => {
|
||||
if (error) {
|
||||
resolve(0)
|
||||
} else {
|
||||
resolve(exifData.image.Orientation)
|
||||
resolve(exifData.image.Orientation || 0)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getTags = (db: Db, imageId: number) => {
|
||||
const getTags = (db: Db, imageId: number): Tag[] => {
|
||||
const query = `
|
||||
select * from categories c
|
||||
inner join image_x_category ixc on c.id = ixc.category_id
|
||||
where ixc.image_id = ?`
|
||||
return db._getMany(query, [imageId])
|
||||
return db._getMany(query, [imageId]).map(row => ({
|
||||
id: parseInt(row.number, 10) || 0,
|
||||
slug: row.slug,
|
||||
title: row.tittle,
|
||||
}))
|
||||
}
|
||||
|
||||
const imageFromDb = (db: Db, imageId: number) => {
|
||||
const imageFromDb = (db: Db, imageId: number): ImageInfo => {
|
||||
const i = db.get('images', { id: imageId })
|
||||
return {
|
||||
id: i.id,
|
||||
|
|
@ -64,21 +92,25 @@ const imageFromDb = (db: Db, imageId: number) => {
|
|||
file: `${UPLOAD_DIR}/${i.filename}`,
|
||||
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
|
||||
title: i.title,
|
||||
tags: getTags(db, i.id) as any[],
|
||||
tags: getTags(db, i.id),
|
||||
created: i.created * 1000,
|
||||
}
|
||||
}
|
||||
|
||||
const allImagesFromDb = (db: Db, tagSlugs: string[], sort: string) => {
|
||||
const sortMap = {
|
||||
const allImagesFromDb = (
|
||||
db: Db,
|
||||
tagSlugs: string[],
|
||||
orderBy: string
|
||||
): ImageInfo[] => {
|
||||
const orderByMap = {
|
||||
alpha_asc: [{filename: 1}],
|
||||
alpha_desc: [{filename: -1}],
|
||||
date_asc: [{created: 1}],
|
||||
date_desc: [{created: -1}],
|
||||
} as Record<string, any>
|
||||
} as Record<string, OrderBy>
|
||||
|
||||
// TODO: .... clean up
|
||||
const wheresRaw: Record<string, any> = {}
|
||||
const wheresRaw: WhereRaw = {}
|
||||
if (tagSlugs.length > 0) {
|
||||
const c = db.getMany('categories', {slug: {'$in': tagSlugs}})
|
||||
if (!c) {
|
||||
|
|
@ -96,7 +128,7 @@ inner join images i on i.id = ixc.image_id ${where.sql};
|
|||
}
|
||||
wheresRaw['id'] = {'$in': ids}
|
||||
}
|
||||
const images = db.getMany('images', wheresRaw, sortMap[sort])
|
||||
const images = db.getMany('images', wheresRaw, orderByMap[orderBy])
|
||||
|
||||
return images.map(i => ({
|
||||
id: i.id as number,
|
||||
|
|
@ -104,12 +136,15 @@ inner join images i on i.id = ixc.image_id ${where.sql};
|
|||
file: `${UPLOAD_DIR}/${i.filename}`,
|
||||
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
|
||||
title: i.title,
|
||||
tags: getTags(db, i.id) as any[],
|
||||
tags: getTags(db, i.id),
|
||||
created: i.created * 1000,
|
||||
}))
|
||||
}
|
||||
|
||||
const allImagesFromDisk = (tags: string[], sort: string) => {
|
||||
const allImagesFromDisk = (
|
||||
tags: string[],
|
||||
sort: string
|
||||
): ImageInfo[] => {
|
||||
let images = fs.readdirSync(UPLOAD_DIR)
|
||||
.filter(f => f.toLowerCase().match(/\.(jpe?g|webp|png)$/))
|
||||
.map(f => ({
|
||||
|
|
@ -118,7 +153,7 @@ const allImagesFromDisk = (tags: string[], sort: string) => {
|
|||
file: `${UPLOAD_DIR}/${f}`,
|
||||
url: `${UPLOAD_URL}/${encodeURIComponent(f)}`,
|
||||
title: f.replace(/\.[a-z]+$/, ''),
|
||||
tags: [] as any[],
|
||||
tags: [] as Tag[],
|
||||
created: fs.statSync(`${UPLOAD_DIR}/${f}`).mtime.getTime(),
|
||||
}))
|
||||
|
||||
|
|
@ -152,7 +187,7 @@ const allImagesFromDisk = (tags: string[], sort: string) => {
|
|||
}
|
||||
|
||||
async function getDimensions(imagePath: string): Promise<Dim> {
|
||||
let dimensions = sizeOf(imagePath)
|
||||
const dimensions = sizeOf(imagePath)
|
||||
const orientation = await getExifOrientation(imagePath)
|
||||
// when image is rotated to the left or right, switch width/height
|
||||
// https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import Util from './../common/Util'
|
||||
import { Rng } from './../common/Rng'
|
||||
import Images from './Images'
|
||||
import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle } from '../common/GameCommon'
|
||||
import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle } from '../common/Types'
|
||||
import { Dim, Point } from '../common/Geometry'
|
||||
|
||||
export interface PuzzleCreationImageInfo {
|
||||
|
|
@ -9,7 +9,7 @@ export interface PuzzleCreationImageInfo {
|
|||
url: string
|
||||
}
|
||||
|
||||
interface PuzzleCreationInfo {
|
||||
export interface PuzzleCreationInfo {
|
||||
width: number
|
||||
height: number
|
||||
tileSize: number
|
||||
|
|
@ -40,16 +40,16 @@ async function createPuzzle(
|
|||
}
|
||||
const info: PuzzleCreationInfo = determinePuzzleInfo(dim, targetTiles)
|
||||
|
||||
let tiles = new Array(info.tiles)
|
||||
for (let i = 0; i < tiles.length; i++) {
|
||||
tiles[i] = { idx: i }
|
||||
const rawPieces = new Array(info.tiles)
|
||||
for (let i = 0; i < rawPieces.length; i++) {
|
||||
rawPieces[i] = { idx: i }
|
||||
}
|
||||
const shapes = determinePuzzleTileShapes(rng, info)
|
||||
|
||||
let positions: Point[] = new Array(info.tiles)
|
||||
for (let tile of tiles) {
|
||||
const coord = Util.coordByTileIdx(info, tile.idx)
|
||||
positions[tile.idx] = {
|
||||
for (const piece of rawPieces) {
|
||||
const coord = Util.coordByPieceIdx(info, piece.idx)
|
||||
positions[piece.idx] = {
|
||||
// instead of info.tileSize, we use info.tileDrawSize
|
||||
// to spread the tiles a bit
|
||||
x: coord.x * info.tileSize * 1.5,
|
||||
|
|
@ -61,7 +61,7 @@ async function createPuzzle(
|
|||
const tableHeight = info.height * 3
|
||||
|
||||
const off = info.tileSize * 1.5
|
||||
let last: Point = {
|
||||
const last: Point = {
|
||||
x: info.width - (1 * off),
|
||||
y: info.height - (2 * off),
|
||||
}
|
||||
|
|
@ -71,7 +71,7 @@ async function createPuzzle(
|
|||
let diffX = off
|
||||
let diffY = 0
|
||||
let index = 0
|
||||
for (let pos of positions) {
|
||||
for (const pos of positions) {
|
||||
pos.x = last.x
|
||||
pos.y = last.y
|
||||
last.x+=diffX
|
||||
|
|
@ -98,9 +98,9 @@ async function createPuzzle(
|
|||
// then shuffle the positions
|
||||
positions = rng.shuffle(positions)
|
||||
|
||||
const pieces: Array<EncodedPiece> = tiles.map(tile => {
|
||||
const pieces: Array<EncodedPiece> = rawPieces.map(piece => {
|
||||
return Util.encodePiece({
|
||||
idx: tile.idx, // index of tile in the array
|
||||
idx: piece.idx, // index of tile in the array
|
||||
group: 0, // if grouped with other tiles
|
||||
z: 0, // z index of the tile
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ async function createPuzzle(
|
|||
// physical current position of the tile (x/y in pixels)
|
||||
// this position is the initial position only and is the
|
||||
// value that changes when moving a tile
|
||||
pos: positions[tile.idx],
|
||||
pos: positions[piece.idx],
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ function determinePuzzleTileShapes(
|
|||
|
||||
const shapes: Array<PieceShape> = new Array(info.tiles)
|
||||
for (let i = 0; i < info.tiles; i++) {
|
||||
let coord = Util.coordByTileIdx(info, i)
|
||||
const coord = Util.coordByPieceIdx(info, i)
|
||||
shapes[i] = {
|
||||
top: coord.y === 0 ? 0 : shapes[i - info.tilesX].bottom * -1,
|
||||
right: coord.x === info.tilesX - 1 ? 0 : rng.choice(tabs),
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ config = {
|
|||
*/
|
||||
|
||||
class EvtBus {
|
||||
private _on: any
|
||||
private _on: Record<string, Function[]>
|
||||
constructor() {
|
||||
this._on = {} as any
|
||||
this._on = {}
|
||||
}
|
||||
|
||||
on(type: string, callback: Function) {
|
||||
on (type: string, callback: Function): void {
|
||||
this._on[type] = this._on[type] || []
|
||||
this._on[type].push(callback)
|
||||
}
|
||||
|
||||
dispatch(type: string, ...args: Array<any>) {
|
||||
dispatch (type: string, ...args: Array<any>): void {
|
||||
(this._on[type] || []).forEach((cb: Function) => {
|
||||
cb(...args)
|
||||
})
|
||||
|
|
@ -43,13 +43,13 @@ class WebSocketServer {
|
|||
this.evt = new EvtBus()
|
||||
}
|
||||
|
||||
on(type: string, callback: Function) {
|
||||
on (type: string, callback: Function): void {
|
||||
this.evt.on(type, callback)
|
||||
}
|
||||
|
||||
listen() {
|
||||
listen (): void {
|
||||
this._websocketserver = new WebSocket.Server(this.config)
|
||||
this._websocketserver.on('connection', (socket: WebSocket, request: Request) => {
|
||||
this._websocketserver.on('connection', (socket: WebSocket, request: Request): void => {
|
||||
const pathname = new URL(this.config.connectstring).pathname
|
||||
if (request.url.indexOf(pathname) !== 0) {
|
||||
log.log('bad request url: ', request.url)
|
||||
|
|
@ -66,13 +66,13 @@ class WebSocketServer {
|
|||
})
|
||||
}
|
||||
|
||||
close() {
|
||||
close (): void {
|
||||
if (this._websocketserver) {
|
||||
this._websocketserver.close()
|
||||
}
|
||||
}
|
||||
|
||||
notifyOne(data: any, socket: WebSocket) {
|
||||
notifyOne (data: any, socket: WebSocket): void {
|
||||
socket.send(JSON.stringify(data))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import multer from 'multer'
|
|||
import Protocol from './../common/Protocol'
|
||||
import Util, { logger } from './../common/Util'
|
||||
import Game from './Game'
|
||||
import bodyParser from 'body-parser'
|
||||
import v8 from 'v8'
|
||||
import fs from 'fs'
|
||||
import GameLog from './GameLog'
|
||||
|
|
@ -19,7 +18,8 @@ import {
|
|||
PUBLIC_DIR,
|
||||
UPLOAD_DIR,
|
||||
} from './Dirs'
|
||||
import GameCommon, { Game as GameType, GameSettings, ScoreMode } from '../common/GameCommon'
|
||||
import GameCommon from '../common/GameCommon'
|
||||
import { Game as GameType, GameSettings, ScoreMode } from '../common/Types'
|
||||
import GameStorage from './GameStorage'
|
||||
import Db from './Db'
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ const setImageTags = (db: Db, imageId: number, tags: string[]) => {
|
|||
})
|
||||
}
|
||||
|
||||
app.post('/api/save-image', bodyParser.json(), (req, res) => {
|
||||
app.post('/api/save-image', express.json(), (req, res) => {
|
||||
const data = req.body as SaveImageRequestData
|
||||
db.update('images', {
|
||||
title: data.title,
|
||||
|
|
@ -189,7 +189,7 @@ app.post('/api/upload', (req, res) => {
|
|||
})
|
||||
})
|
||||
|
||||
app.post('/newgame', bodyParser.json(), async (req, res) => {
|
||||
app.post('/newgame', express.json(), async (req, res) => {
|
||||
const gameSettings = req.body as GameSettings
|
||||
log.log(gameSettings)
|
||||
const gameId = Util.uniqId()
|
||||
|
|
@ -212,8 +212,7 @@ app.use('/', express.static(PUBLIC_DIR))
|
|||
const wss = new WebSocketServer(config.ws);
|
||||
|
||||
const notify = (data: any, sockets: Array<WebSocket>) => {
|
||||
// TODO: throttle?
|
||||
for (let socket of sockets) {
|
||||
for (const socket of sockets) {
|
||||
wss.notifyOne(data, socket)
|
||||
}
|
||||
}
|
||||
|
|
@ -221,7 +220,7 @@ const notify = (data: any, sockets: Array<WebSocket>) => {
|
|||
wss.on('close', async ({socket} : {socket: WebSocket}) => {
|
||||
try {
|
||||
const proto = socket.protocol.split('|')
|
||||
const clientId = proto[0]
|
||||
// const clientId = proto[0]
|
||||
const gameId = proto[1]
|
||||
GameSockets.removeSocket(gameId, socket)
|
||||
} catch (e) {
|
||||
|
|
@ -244,7 +243,11 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
|
|||
const ts = Time.timestamp()
|
||||
Game.addPlayer(gameId, clientId, ts)
|
||||
GameSockets.addSocket(gameId, socket)
|
||||
const game: GameType = GameCommon.get(gameId)
|
||||
|
||||
const game: GameType|null = GameCommon.get(gameId)
|
||||
if (!game) {
|
||||
throw `[game ${gameId} does not exist (anymore)... ]`
|
||||
}
|
||||
notify(
|
||||
[Protocol.EV_SERVER_INIT, Util.encodeGame(game)],
|
||||
[socket]
|
||||
|
|
@ -269,7 +272,10 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
|
|||
sendGame = true
|
||||
}
|
||||
if (sendGame) {
|
||||
const game: GameType = GameCommon.get(gameId)
|
||||
const game: GameType|null = GameCommon.get(gameId)
|
||||
if (!game) {
|
||||
throw `[game ${gameId} does not exist (anymore)... ]`
|
||||
}
|
||||
notify(
|
||||
[Protocol.EV_SERVER_INIT, Util.encodeGame(game)],
|
||||
[socket]
|
||||
|
|
@ -299,7 +305,7 @@ wss.listen()
|
|||
|
||||
const memoryUsageHuman = () => {
|
||||
const totalHeapSize = v8.getHeapStatistics().total_available_size
|
||||
let totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2)
|
||||
const totalHeapSizeInGB = (totalHeapSize / 1024 / 1024 / 1024).toFixed(2)
|
||||
|
||||
log.log(`Total heap size (bytes) ${totalHeapSize}, (GB ~${totalHeapSizeInGB})`)
|
||||
const used = process.memoryUsage().heapUsed / 1024 / 1024
|
||||
|
|
@ -340,10 +346,10 @@ process.once('SIGUSR2', function () {
|
|||
gracefulShutdown('SIGUSR2')
|
||||
})
|
||||
|
||||
process.once('SIGINT', function (code) {
|
||||
process.once('SIGINT', function () {
|
||||
gracefulShutdown('SIGINT')
|
||||
})
|
||||
|
||||
process.once('SIGTERM', function (code) {
|
||||
process.once('SIGTERM', function () {
|
||||
gracefulShutdown('SIGTERM')
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue