type hints galore!

This commit is contained in:
Zutatensuppe 2021-05-29 15:36:03 +02:00
parent 7b1f270587
commit 46f3fc7480
17 changed files with 700 additions and 667 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>🧩 jigsaw.hyottoko.club</title> <title>🧩 jigsaw.hyottoko.club</title>
<script type="module" crossorigin src="/assets/index.8f906b9e.js"></script> <script type="module" crossorigin src="/assets/index.643c957c.js"></script>
<link rel="modulepreload" href="/assets/vendor.b622ee49.js"> <link rel="modulepreload" href="/assets/vendor.b622ee49.js">
<link rel="stylesheet" href="/assets/index.f7304069.css"> <link rel="stylesheet" href="/assets/index.f7304069.css">
</head> </head>

File diff suppressed because it is too large Load diff

View file

@ -7,22 +7,22 @@ const log = logger('fix_tiles.js')
function fix_tiles(gameId) { function fix_tiles(gameId) {
GameStorage.loadGame(gameId) GameStorage.loadGame(gameId)
let changed = false let changed = false
const tiles = GameCommon.getTilesSortedByZIndex(gameId) const tiles = GameCommon.getPiecesSortedByZIndex(gameId)
for (let tile of tiles) { for (let tile of tiles) {
if (tile.owner === -1) { if (tile.owner === -1) {
const p = GameCommon.getFinalTilePos(gameId, tile.idx) const p = GameCommon.getFinalPiecePos(gameId, tile.idx)
if (p.x === tile.pos.x && p.y === tile.pos.y) { if (p.x === tile.pos.x && p.y === tile.pos.y) {
// log.log('all good', tile.pos) // log.log('all good', tile.pos)
} else { } else {
log.log('bad tile pos', tile.pos, 'should be: ', p) log.log('bad tile pos', tile.pos, 'should be: ', p)
tile.pos = p tile.pos = p
GameCommon.setTile(gameId, tile.idx, tile) GameCommon.setPiece(gameId, tile.idx, tile)
changed = true changed = true
} }
} else if (tile.owner !== 0) { } else if (tile.owner !== 0) {
tile.owner = 0 tile.owner = 0
log.log('unowning tile', tile.idx) log.log('unowning tile', tile.idx)
GameCommon.setTile(gameId, tile.idx, tile) GameCommon.setPiece(gameId, tile.idx, tile)
changed = true changed = true
} }
} }

View file

@ -1,14 +1,50 @@
import Geometry, { Point, Rect } from './Geometry' import Geometry, { Point, Rect } from './Geometry'
import Protocol from './Protocol' import Protocol from './Protocol'
import { Rng } from './Rng' import { Rng, RngSerialized } from './Rng'
import Time from './Time' import Time from './Time'
import { FixedLengthArray } from './Types'
import Util from './Util' import Util from './Util'
export type Timestamp = number export type Timestamp = number
export type EncodedPlayer = Array<any>
export type EncodedPiece = 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 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 { export interface Tag {
id: number id: number
slug: string slug: string
@ -20,7 +56,7 @@ interface GameRng {
type?: string type?: string
} }
interface Game { export interface Game {
id: string id: string
players: Array<EncodedPlayer> players: Array<EncodedPlayer>
puzzle: Puzzle puzzle: Puzzle
@ -112,7 +148,7 @@ export interface Player {
color: string|null color: string|null
bgcolor: string|null bgcolor: string|null
points: number points: number
ts: number ts: Timestamp
} }
interface EvtInfo { interface EvtInfo {
@ -134,7 +170,7 @@ function exists(gameId: string) {
return (!!GAMES[gameId]) || false return (!!GAMES[gameId]) || false
} }
function __createPlayerObject(id: string, ts: number): Player { function __createPlayerObject(id: string, ts: Timestamp): Player {
return { return {
id: id, id: id,
x: 0, x: 0,
@ -191,8 +227,8 @@ function setPlayer(
} }
} }
function setTile(gameId: string, tileIdx: number, tile: Piece): void { function setPiece(gameId: string, pieceIdx: number, piece: Piece): void {
GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile) GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece)
} }
function setPuzzleData(gameId: string, data: PuzzleData): void { function setPuzzleData(gameId: string, data: PuzzleData): void {
@ -214,7 +250,7 @@ function getIdlePlayers(gameId: string, ts: number): Array<Player> {
return getAllPlayers(gameId).filter((p: Player) => p.ts < minTs && p.points > 0) return getAllPlayers(gameId).filter((p: Player) => p.ts < minTs && p.points > 0)
} }
function addPlayer(gameId: string, playerId: string, ts: number): void { function addPlayer(gameId: string, playerId: string, ts: Timestamp): void {
if (!playerExists(gameId, playerId)) { if (!playerExists(gameId, playerId)) {
setPlayer(gameId, playerId, __createPlayerObject(playerId, ts)) setPlayer(gameId, playerId, __createPlayerObject(playerId, ts))
} else { } else {
@ -261,7 +297,7 @@ function get(gameId: string) {
return GAMES[gameId] return GAMES[gameId]
} }
function getTileCount(gameId: string): number { function getPieceCount(gameId: string): number {
return GAMES[gameId].puzzle.tiles.length return GAMES[gameId].puzzle.tiles.length
} }
@ -278,22 +314,22 @@ function getScoreMode(gameId: string): ScoreMode {
} }
function isFinished(gameId: string): boolean { function isFinished(gameId: string): boolean {
return getFinishedTileCount(gameId) === getTileCount(gameId) return getFinishedPiecesCount(gameId) === getPieceCount(gameId)
} }
function getFinishedTileCount(gameId: string): number { function getFinishedPiecesCount(gameId: string): number {
let count = 0 let count = 0
for (let t of GAMES[gameId].puzzle.tiles) { for (let t of GAMES[gameId].puzzle.tiles) {
if (Util.decodeTile(t).owner === -1) { if (Util.decodePiece(t).owner === -1) {
count++ count++
} }
} }
return count return count
} }
function getTilesSortedByZIndex(gameId: string): Piece[] { function getPiecesSortedByZIndex(gameId: string): Piece[] {
const tiles = GAMES[gameId].puzzle.tiles.map(Util.decodeTile) const pieces = GAMES[gameId].puzzle.tiles.map(Util.decodePiece)
return tiles.sort((t1, t2) => t1.z - t2.z) return pieces.sort((t1, t2) => t1.z - t2.z)
} }
function changePlayer( function changePlayer(
@ -320,25 +356,25 @@ function changeData(gameId: string, change: any): void {
} }
} }
function changeTile(gameId: string, tileIdx: number, change: any): void { function changeTile(gameId: string, pieceIdx: number, change: any): void {
for (let k of Object.keys(change)) { for (let k of Object.keys(change)) {
const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) const piece = Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx])
// @ts-ignore // @ts-ignore
tile[k] = change[k] piece[k] = change[k]
GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile) GAMES[gameId].puzzle.tiles[pieceIdx] = Util.encodePiece(piece)
} }
} }
const getTile = (gameId: string, tileIdx: number): Piece => { const getPiece = (gameId: string, pieceIdx: number): Piece => {
return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) return Util.decodePiece(GAMES[gameId].puzzle.tiles[pieceIdx])
} }
const getTileGroup = (gameId: string, tileIdx: number): number => { const getPieceGroup = (gameId: string, tileIdx: number): number => {
const tile = getTile(gameId, tileIdx) const tile = getPiece(gameId, tileIdx)
return tile.group return tile.group
} }
const getFinalTilePos = (gameId: string, tileIdx: number): Point => { const getFinalPiecePos = (gameId: string, tileIdx: number): Point => {
const info = GAMES[gameId].puzzle.info const info = GAMES[gameId].puzzle.info
const boardPos = { const boardPos = {
x: (info.table.width - info.width) / 2, x: (info.table.width - info.width) / 2,
@ -348,8 +384,8 @@ const getFinalTilePos = (gameId: string, tileIdx: number): Point => {
return Geometry.pointAdd(boardPos, srcPos) return Geometry.pointAdd(boardPos, srcPos)
} }
const getTilePos = (gameId: string, tileIdx: number): Point => { const getPiecePos = (gameId: string, tileIdx: number): Point => {
const tile = getTile(gameId, tileIdx) const tile = getPiece(gameId, tileIdx)
return tile.pos return tile.pos
} }
@ -368,9 +404,9 @@ const getBounds = (gameId: string): Rect => {
} }
} }
const getTileBounds = (gameId: string, tileIdx: number): Rect => { const getPieceBounds = (gameId: string, tileIdx: number): Rect => {
const s = getTileSize(gameId) const s = getPieceSize(gameId)
const tile = getTile(gameId, tileIdx) const tile = getPiece(gameId, tileIdx)
return { return {
x: tile.pos.x, x: tile.pos.x,
y: tile.pos.y, y: tile.pos.y,
@ -380,13 +416,13 @@ const getTileBounds = (gameId: string, tileIdx: number): Rect => {
} }
const getTileZIndex = (gameId: string, tileIdx: number): number => { const getTileZIndex = (gameId: string, tileIdx: number): number => {
const tile = getTile(gameId, tileIdx) const tile = getPiece(gameId, tileIdx)
return tile.z return tile.z
} }
const getFirstOwnedTileIdx = (gameId: string, playerId: string): number => { const getFirstOwnedPieceIdx = (gameId: string, playerId: string): number => {
for (let t of GAMES[gameId].puzzle.tiles) { for (let t of GAMES[gameId].puzzle.tiles) {
const tile = Util.decodeTile(t) const tile = Util.decodePiece(t)
if (tile.owner === playerId) { if (tile.owner === playerId) {
return tile.idx return tile.idx
} }
@ -394,20 +430,20 @@ const getFirstOwnedTileIdx = (gameId: string, playerId: string): number => {
return -1 return -1
} }
const getFirstOwnedTile = (gameId: string, playerId: string): EncodedPiece|null => { const getFirstOwnedPiece = (gameId: string, playerId: string): EncodedPiece|null => {
const idx = getFirstOwnedTileIdx(gameId, playerId) const idx = getFirstOwnedPieceIdx(gameId, playerId)
return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx] return idx < 0 ? null : GAMES[gameId].puzzle.tiles[idx]
} }
const getTileDrawOffset = (gameId: string): number => { const getPieceDrawOffset = (gameId: string): number => {
return GAMES[gameId].puzzle.info.tileDrawOffset return GAMES[gameId].puzzle.info.tileDrawOffset
} }
const getTileDrawSize = (gameId: string): number => { const getPieceDrawSize = (gameId: string): number => {
return GAMES[gameId].puzzle.info.tileDrawSize return GAMES[gameId].puzzle.info.tileDrawSize
} }
const getTileSize = (gameId: string): number => { const getPieceSize = (gameId: string): number => {
return GAMES[gameId].puzzle.info.tileSize return GAMES[gameId].puzzle.info.tileSize
} }
@ -472,7 +508,7 @@ const setTilesZIndex = (gameId: string, tileIdxs: Array<number>, zIndex: number)
} }
const moveTileDiff = (gameId: string, tileIdx: number, diff: Point): void => { const moveTileDiff = (gameId: string, tileIdx: number, diff: Point): void => {
const oldPos = getTilePos(gameId, tileIdx) const oldPos = getPiecePos(gameId, tileIdx)
const pos = Geometry.pointAdd(oldPos, diff) const pos = Geometry.pointAdd(oldPos, diff)
changeTile(gameId, tileIdx, { pos }) changeTile(gameId, tileIdx, { pos })
} }
@ -482,21 +518,21 @@ const moveTilesDiff = (
tileIdxs: Array<number>, tileIdxs: Array<number>,
diff: Point diff: Point
): void => { ): void => {
const tileDrawSize = getTileDrawSize(gameId) const drawSize = getPieceDrawSize(gameId)
const bounds = getBounds(gameId) const bounds = getBounds(gameId)
const cappedDiff = diff const cappedDiff = diff
for (let tileIdx of tileIdxs) { for (let tileIdx of tileIdxs) {
const t = getTile(gameId, tileIdx) const t = getPiece(gameId, tileIdx)
if (t.pos.x + diff.x < bounds.x) { if (t.pos.x + diff.x < bounds.x) {
cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x) cappedDiff.x = Math.max(bounds.x - t.pos.x, cappedDiff.x)
} else if (t.pos.x + tileDrawSize + diff.x > bounds.x + bounds.w) { } else if (t.pos.x + drawSize + diff.x > bounds.x + bounds.w) {
cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + tileDrawSize, cappedDiff.x) cappedDiff.x = Math.min(bounds.x + bounds.w - t.pos.x + drawSize, cappedDiff.x)
} }
if (t.pos.y + diff.y < bounds.y) { if (t.pos.y + diff.y < bounds.y) {
cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y) cappedDiff.y = Math.max(bounds.y - t.pos.y, cappedDiff.y)
} else if (t.pos.y + tileDrawSize + diff.y > bounds.y + bounds.h) { } else if (t.pos.y + drawSize + diff.y > bounds.y + bounds.h) {
cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + tileDrawSize, cappedDiff.y) cappedDiff.y = Math.min(bounds.y + bounds.h - t.pos.y + drawSize, cappedDiff.y)
} }
} }
@ -522,52 +558,52 @@ const setTilesOwner = (
} }
// get all grouped tiles for a tile // get all grouped tiles for a tile
function getGroupedTileIdxs(gameId: string, tileIdx: number): number[] { function getGroupedPieceIdxs(gameId: string, pieceIdx: number): number[] {
const tiles = GAMES[gameId].puzzle.tiles const pieces = GAMES[gameId].puzzle.tiles
const tile = Util.decodeTile(tiles[tileIdx]) const piece = Util.decodePiece(pieces[pieceIdx])
const grouped = [] const grouped = []
if (tile.group) { if (piece.group) {
for (let other of tiles) { for (let other of pieces) {
const otherTile = Util.decodeTile(other) const otherPiece = Util.decodePiece(other)
if (otherTile.group === tile.group) { if (otherPiece.group === piece.group) {
grouped.push(otherTile.idx) grouped.push(otherPiece.idx)
} }
} }
} else { } else {
grouped.push(tile.idx) grouped.push(piece.idx)
} }
return grouped return grouped
} }
// Returns the index of the puzzle tile with the highest z index // Returns the index of the puzzle tile with the highest z index
// that is not finished yet and that matches the position // that is not finished yet and that matches the position
const freeTileIdxByPos = (gameId: string, pos: Point): number => { const freePieceIdxByPos = (gameId: string, pos: Point): number => {
let info = GAMES[gameId].puzzle.info let info = GAMES[gameId].puzzle.info
let tiles = GAMES[gameId].puzzle.tiles let pieces = GAMES[gameId].puzzle.tiles
let maxZ = -1 let maxZ = -1
let tileIdx = -1 let pieceIdx = -1
for (let idx = 0; idx < tiles.length; idx++) { for (let idx = 0; idx < pieces.length; idx++) {
const tile = Util.decodeTile(tiles[idx]) const piece = Util.decodePiece(pieces[idx])
if (tile.owner !== 0) { if (piece.owner !== 0) {
continue continue
} }
const collisionRect: Rect = { const collisionRect: Rect = {
x: tile.pos.x, x: piece.pos.x,
y: tile.pos.y, y: piece.pos.y,
w: info.tileSize, w: info.tileSize,
h: info.tileSize, h: info.tileSize,
} }
if (Geometry.pointInBounds(pos, collisionRect)) { if (Geometry.pointInBounds(pos, collisionRect)) {
if (maxZ === -1 || tile.z > maxZ) { if (maxZ === -1 || piece.z > maxZ) {
maxZ = tile.z maxZ = piece.z
tileIdx = idx pieceIdx = idx
} }
} }
} }
return tileIdx return pieceIdx
} }
const getPlayerBgColor = (gameId: string, playerId: string): string|null => { const getPlayerBgColor = (gameId: string, playerId: string): string|null => {
@ -596,8 +632,8 @@ const areGrouped = (
tileIdx1: number, tileIdx1: number,
tileIdx2: number tileIdx2: number
): boolean => { ): boolean => {
const g1 = getTileGroup(gameId, tileIdx1) const g1 = getPieceGroup(gameId, tileIdx1)
const g2 = getTileGroup(gameId, tileIdx2) const g2 = getPieceGroup(gameId, tileIdx2)
return !!(g1 && g1 === g2) return !!(g1 && g1 === g2)
} }
@ -643,7 +679,7 @@ function handleInput(
const _tileChange = (tileIdx: number): void => { const _tileChange = (tileIdx: number): void => {
changes.push([ changes.push([
Protocol.CHANGE_TILE, Protocol.CHANGE_TILE,
Util.encodeTile(getTile(gameId, tileIdx)), Util.encodePiece(getPiece(gameId, tileIdx)),
]) ])
} }
@ -671,8 +707,8 @@ function handleInput(
tileIdx2: number tileIdx2: number
): void => { ): void => {
const tiles = GAMES[gameId].puzzle.tiles const tiles = GAMES[gameId].puzzle.tiles
const group1 = getTileGroup(gameId, tileIdx1) const group1 = getPieceGroup(gameId, tileIdx1)
const group2 = getTileGroup(gameId, tileIdx2) const group2 = getPieceGroup(gameId, tileIdx2)
let group let group
const searchGroups = [] const searchGroups = []
@ -701,10 +737,10 @@ function handleInput(
// TODO: strange // TODO: strange
if (searchGroups.length > 0) { if (searchGroups.length > 0) {
for (const t of tiles) { for (const t of tiles) {
const tile = Util.decodeTile(t) const piece = Util.decodePiece(t)
if (searchGroups.includes(tile.group)) { if (searchGroups.includes(piece.group)) {
changeTile(gameId, tile.idx, { group }) changeTile(gameId, piece.idx, { group })
_tileChange(tile.idx) _tileChange(piece.idx)
} }
} }
} }
@ -732,12 +768,12 @@ function handleInput(
_playerChange() _playerChange()
evtInfo._last_mouse_down = pos evtInfo._last_mouse_down = pos
const tileIdxAtPos = freeTileIdxByPos(gameId, pos) const tileIdxAtPos = freePieceIdxByPos(gameId, pos)
if (tileIdxAtPos >= 0) { if (tileIdxAtPos >= 0) {
let maxZ = getMaxZIndex(gameId) + 1 let maxZ = getMaxZIndex(gameId) + 1
changeData(gameId, { maxZ }) changeData(gameId, { maxZ })
_dataChange() _dataChange()
const tileIdxs = getGroupedTileIdxs(gameId, tileIdxAtPos) const tileIdxs = getGroupedPieceIdxs(gameId, tileIdxAtPos)
setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId)) setTilesZIndex(gameId, tileIdxs, getMaxZIndex(gameId))
setTilesOwner(gameId, tileIdxs, playerId) setTilesOwner(gameId, tileIdxs, playerId)
_tileChanges(tileIdxs) _tileChanges(tileIdxs)
@ -754,7 +790,7 @@ function handleInput(
changePlayer(gameId, playerId, {x, y, ts}) changePlayer(gameId, playerId, {x, y, ts})
_playerChange() _playerChange()
} else { } else {
let tileIdx = getFirstOwnedTileIdx(gameId, playerId) let tileIdx = getFirstOwnedPieceIdx(gameId, playerId)
if (tileIdx >= 0) { if (tileIdx >= 0) {
// player is moving a tile (and hand) // player is moving a tile (and hand)
changePlayer(gameId, playerId, {x, y, ts}) changePlayer(gameId, playerId, {x, y, ts})
@ -762,11 +798,11 @@ function handleInput(
// check if pos is on the tile, otherwise dont move // check if pos is on the tile, otherwise dont move
// (mouse could be out of table, but tile stays on it) // (mouse could be out of table, but tile stays on it)
const tileIdxs = getGroupedTileIdxs(gameId, tileIdx) const tileIdxs = getGroupedPieceIdxs(gameId, tileIdx)
let anyOk = Geometry.pointInBounds(pos, getBounds(gameId)) let anyOk = Geometry.pointInBounds(pos, getBounds(gameId))
&& Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId)) && Geometry.pointInBounds(evtInfo._last_mouse_down, getBounds(gameId))
for (let idx of tileIdxs) { for (let idx of tileIdxs) {
const bounds = getTileBounds(gameId, idx) const bounds = getPieceBounds(gameId, idx)
if (Geometry.pointInBounds(pos, bounds)) { if (Geometry.pointInBounds(pos, bounds)) {
anyOk = true anyOk = true
break break
@ -799,16 +835,16 @@ function handleInput(
evtInfo._last_mouse_down = null evtInfo._last_mouse_down = null
let tileIdx = getFirstOwnedTileIdx(gameId, playerId) let tileIdx = getFirstOwnedPieceIdx(gameId, playerId)
if (tileIdx >= 0) { if (tileIdx >= 0) {
// drop the tile(s) // drop the tile(s)
let tileIdxs = getGroupedTileIdxs(gameId, tileIdx) let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx)
setTilesOwner(gameId, tileIdxs, 0) setTilesOwner(gameId, tileIdxs, 0)
_tileChanges(tileIdxs) _tileChanges(tileIdxs)
// Check if the tile was dropped near the final location // Check if the tile was dropped near the final location
let tilePos = getTilePos(gameId, tileIdx) let tilePos = getPiecePos(gameId, tileIdx)
let finalPos = getFinalTilePos(gameId, tileIdx) let finalPos = getFinalPiecePos(gameId, tileIdx)
if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) { if (Geometry.pointDistance(finalPos, tilePos) < puzzle.info.snapDistance) {
let diff = Geometry.pointSub(finalPos, tilePos) let diff = Geometry.pointSub(finalPos, tilePos)
// Snap the tile to the final destination // Snap the tile to the final destination
@ -829,7 +865,7 @@ function handleInput(
_playerChange() _playerChange()
// check if the puzzle is finished // check if the puzzle is finished
if (getFinishedTileCount(gameId) === getTileCount(gameId)) { if (getFinishedPiecesCount(gameId) === getPieceCount(gameId)) {
changeData(gameId, { finished: ts }) changeData(gameId, { finished: ts })
_dataChange() _dataChange()
} }
@ -848,17 +884,17 @@ function handleInput(
if (areGrouped(gameId, tileIdx, otherTileIdx)) { if (areGrouped(gameId, tileIdx, otherTileIdx)) {
return false return false
} }
const tilePos = getTilePos(gameId, tileIdx) const tilePos = getPiecePos(gameId, tileIdx)
const dstPos = Geometry.pointAdd( const dstPos = Geometry.pointAdd(
getTilePos(gameId, otherTileIdx), getPiecePos(gameId, otherTileIdx),
{x: off[0] * info.tileSize, y: off[1] * info.tileSize} {x: off[0] * info.tileSize, y: off[1] * info.tileSize}
) )
if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) { if (Geometry.pointDistance(tilePos, dstPos) < info.snapDistance) {
let diff = Geometry.pointSub(dstPos, tilePos) let diff = Geometry.pointSub(dstPos, tilePos)
let tileIdxs = getGroupedTileIdxs(gameId, tileIdx) let tileIdxs = getGroupedPieceIdxs(gameId, tileIdx)
moveTilesDiff(gameId, tileIdxs, diff) moveTilesDiff(gameId, tileIdxs, diff)
groupTiles(gameId, tileIdx, otherTileIdx) groupTiles(gameId, tileIdx, otherTileIdx)
tileIdxs = getGroupedTileIdxs(gameId, tileIdx) tileIdxs = getGroupedPieceIdxs(gameId, tileIdx)
const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs) const zIndex = getMaxZIndexByTileIdxs(gameId, tileIdxs)
setTilesZIndex(gameId, tileIdxs, zIndex) setTilesZIndex(gameId, tileIdxs, zIndex)
_tileChanges(tileIdxs) _tileChanges(tileIdxs)
@ -868,7 +904,7 @@ function handleInput(
} }
let snapped = false let snapped = false
for (let tileIdxTmp of getGroupedTileIdxs(gameId, tileIdx)) { for (let tileIdxTmp of getGroupedPieceIdxs(gameId, tileIdx)) {
let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp) let othersIdxs = getSurroundingTilesByIdx(gameId, tileIdxTmp)
if ( if (
check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top check(gameId, tileIdxTmp, othersIdxs[0], [0, 1]) // top
@ -916,15 +952,14 @@ function handleInput(
} }
export default { export default {
__createPlayerObject,
setGame, setGame,
exists, exists,
playerExists, playerExists,
getActivePlayers, getActivePlayers,
getIdlePlayers, getIdlePlayers,
addPlayer, addPlayer,
getFinishedTileCount, getFinishedPiecesCount,
getTileCount, getPieceCount,
getImageUrl, getImageUrl,
setImageUrl, setImageUrl,
get, get,
@ -936,7 +971,7 @@ export default {
getPlayerIdByIndex, getPlayerIdByIndex,
changePlayer, changePlayer,
setPlayer, setPlayer,
setTile, setPiece,
setPuzzleData, setPuzzleData,
getTableWidth, getTableWidth,
getTableHeight, getTableHeight,
@ -944,11 +979,11 @@ export default {
getRng, getRng,
getPuzzleWidth, getPuzzleWidth,
getPuzzleHeight, getPuzzleHeight,
getTilesSortedByZIndex, getPiecesSortedByZIndex,
getFirstOwnedTile, getFirstOwnedPiece,
getTileDrawOffset, getPieceDrawOffset,
getTileDrawSize, getPieceDrawSize,
getFinalTilePos, getFinalPiecePos,
getStartTs, getStartTs,
getFinishTs, getFinishTs,
handleInput, handleInput,

View file

@ -1,4 +1,4 @@
interface RngSerialized { export interface RngSerialized {
rand_high: number, rand_high: number,
rand_low: number, rand_low: number,
} }

6
src/common/Types.ts Normal file
View file

@ -0,0 +1,6 @@
// @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> > }

View file

@ -1,4 +1,14 @@
import { EncodedPiece, EncodedPieceShape, EncodedPlayer, Piece, PieceShape, Player } from './GameCommon' import {
EncodedGame,
EncodedPiece,
EncodedPieceShape,
EncodedPlayer,
Game,
Piece,
PieceShape,
Player,
ScoreMode
} from './GameCommon'
import { Point } from './Geometry' import { Point } from './Geometry'
import { Rng } from './Rng' import { Rng } from './Rng'
@ -58,11 +68,11 @@ function decodeShape(data: EncodedPieceShape): PieceShape {
} }
} }
function encodeTile(data: Piece): EncodedPiece { function encodePiece(data: Piece): EncodedPiece {
return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group] return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group]
} }
function decodeTile(data: EncodedPiece): Piece { function decodePiece(data: EncodedPiece): Piece {
return { return {
idx: data[0], idx: data[0],
pos: { pos: {
@ -103,25 +113,19 @@ function decodePlayer(data: EncodedPlayer): Player {
} }
} }
function encodeGame(data: any): Array<any> { function encodeGame(data: Game): EncodedGame {
if (Array.isArray(data)) {
return data
}
return [ return [
data.id, data.id,
data.rng.type, data.rng.type || '',
Rng.serialize(data.rng.obj), Rng.serialize(data.rng.obj),
data.puzzle, data.puzzle,
data.players, data.players,
data.evtInfos, data.evtInfos,
data.scoreMode, data.scoreMode || ScoreMode.FINAL,
] ]
} }
function decodeGame(data: any) { function decodeGame(data: EncodedGame): Game {
if (!Array.isArray(data)) {
return data
}
return { return {
id: data[0], id: data[0],
rng: { rng: {
@ -174,8 +178,8 @@ export default {
encodeShape, encodeShape,
decodeShape, decodeShape,
encodeTile, encodePiece,
decodeTile, decodePiece,
encodePlayer, encodePlayer,
decodePlayer, decodePlayer,

View file

@ -1,6 +1,7 @@
"use strict" "use strict"
import { logger } from '../common/Util' import { EncodedGame, ReplayData } from '../common/GameCommon'
import Util, { logger } from '../common/Util'
import Protocol from './../common/Protocol' import Protocol from './../common/Protocol'
const log = logger('Communication.js') const log = logger('Communication.js')
@ -51,7 +52,7 @@ function connect(
address: string, address: string,
gameId: string, gameId: string,
clientId: string clientId: string
): Promise<any> { ): Promise<EncodedGame> {
clientSeq = 0 clientSeq = 0
events = {} events = {}
setConnectionState(CONN_STATE_CONNECTING) setConnectionState(CONN_STATE_CONNECTING)
@ -100,9 +101,10 @@ async function requestReplayData(
gameId: string, gameId: string,
offset: number, offset: number,
size: number size: number
): Promise<{ log: Array<any>, game: any }> { ): Promise<ReplayData> {
const res = await fetch(`/api/replay-data?gameId=${gameId}&offset=${offset}&size=${size}`) const args = { gameId, offset, size }
const json: { log: Array<any>, game: any } = await res.json() const res = await fetch(`/api/replay-data${Util.asQueryArgs(args)}`)
const json: ReplayData = await res.json()
return json return json
} }

View file

@ -94,7 +94,7 @@ async function createPuzzleTileBitmaps(
const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D
for (const t of tiles) { for (const t of tiles) {
const tile = Util.decodeTile(t) const tile = Util.decodePiece(t)
const srcRect = srcRectByIdx(info, tile.idx) const srcRect = srcRectByIdx(info, tile.idx)
const path = pathForShape(Util.decodeShape(info.shapes[tile.idx])) const path = pathForShape(Util.decodeShape(info.shapes[tile.idx]))

View file

@ -7,11 +7,12 @@ import Debug from './Debug'
import Communication from './Communication' import Communication from './Communication'
import Util from './../common/Util' import Util from './../common/Util'
import PuzzleGraphics from './PuzzleGraphics' import PuzzleGraphics from './PuzzleGraphics'
import Game, { Player, Piece } from './../common/GameCommon' import Game, { Game as GameType, Player, Piece, EncodedGame, ReplayData, Timestamp } from './../common/GameCommon'
import fireworksController from './Fireworks' import fireworksController from './Fireworks'
import Protocol from '../common/Protocol' import Protocol from '../common/Protocol'
import Time from '../common/Time' import Time from '../common/Time'
import { Dim, Point } from '../common/Geometry' import { Dim, Point } from '../common/Geometry'
import { FixedLengthArray } from '../common/Types'
declare global { declare global {
interface Window { interface Window {
@ -19,13 +20,6 @@ declare global {
} }
} }
// @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
type FixedLengthArray<T extends any[]> =
Pick<T, Exclude<keyof T, ArrayLengthMutationKeys>>
& { [Symbol.iterator]: () => IterableIterator< ArrayItems<T> > }
// @ts-ignore // @ts-ignore
const images = import.meta.globEager('./*.png') const images = import.meta.globEager('./*.png')
@ -50,15 +44,17 @@ interface Hud {
interface Replay { interface Replay {
final: boolean final: boolean
requesting: boolean requesting: boolean
log: Array<any> log: Array<any> // current log entries
logPointer: number, logPointer: number // pointer to current item in the log array
logIdx: number
speeds: Array<number> speeds: Array<number>
speedIdx: number speedIdx: number
paused: boolean paused: boolean
lastRealTs: number lastRealTs: number
lastGameTs: number lastGameTs: number
gameStartTs: number gameStartTs: number
//
dataOffset: number
dataSize: number
} }
const shouldDrawPiece = (piece: Piece) => { const shouldDrawPiece = (piece: Piece) => {
@ -267,51 +263,61 @@ export async function main(
requesting: true, requesting: true,
log: [], log: [],
logPointer: 0, logPointer: 0,
logIdx: 0,
speeds: [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500], speeds: [0.5, 1, 2, 5, 10, 20, 50, 100, 250, 500],
speedIdx: 1, speedIdx: 1,
paused: false, paused: false,
lastRealTs: 0, lastRealTs: 0,
lastGameTs: 0, lastGameTs: 0,
gameStartTs: 0, gameStartTs: 0,
dataOffset: 0,
dataSize: 10000,
} }
Communication.onConnectionStateChange((state) => { Communication.onConnectionStateChange((state) => {
HUD.setConnectionState(state) HUD.setConnectionState(state)
}) })
const queryNextReplayBatch = async (
gameId: string
): Promise<ReplayData> => {
REPLAY.requesting = true
const replay: ReplayData = await Communication.requestReplayData(
gameId,
REPLAY.dataOffset,
REPLAY.dataSize
)
REPLAY.dataOffset += REPLAY.dataSize
REPLAY.requesting = false
return replay
}
const getNextReplayBatch = async ( const getNextReplayBatch = async (
gameId: string, gameId: string
offset: number,
size: number
) => { ) => {
const replay: { const replay: ReplayData = await queryNextReplayBatch(gameId)
game: any,
log: Array<any>
} = await Communication.requestReplayData(gameId, offset, size)
// cut log that was already handled // cut log that was already handled
REPLAY.log = REPLAY.log.slice(REPLAY.logPointer) REPLAY.log = REPLAY.log.slice(REPLAY.logPointer)
REPLAY.logPointer = 0 REPLAY.logPointer = 0
REPLAY.log.push(...replay.log) REPLAY.log.push(...replay.log)
if (replay.log.length < 10000) { if (replay.log.length < REPLAY.dataSize) {
REPLAY.final = true REPLAY.final = true
} }
REPLAY.requesting = false
} }
let TIME: () => number = () => 0 let TIME: () => number = () => 0
const connect = async () => { const connect = async () => {
if (MODE === MODE_PLAY) { if (MODE === MODE_PLAY) {
const game = await Communication.connect(wsAddress, gameId, clientId) const game: EncodedGame = await Communication.connect(wsAddress, gameId, clientId)
const gameObject = Util.decodeGame(game) const gameObject: GameType = Util.decodeGame(game)
Game.setGame(gameObject.id, gameObject) Game.setGame(gameObject.id, gameObject)
TIME = () => Time.timestamp() TIME = () => Time.timestamp()
} else if (MODE === MODE_REPLAY) { } else if (MODE === MODE_REPLAY) {
const replay: { const replay: ReplayData = await queryNextReplayBatch(gameId)
game: any, if (!replay.game) {
log: Array<any> throw '[ 2021-05-29 no game received ]'
} = await Communication.requestReplayData(gameId, 0, 10000) }
const gameObject = Util.decodeGame(replay.game) const gameObject: GameType = Util.decodeGame(replay.game)
Game.setGame(gameObject.id, gameObject) Game.setGame(gameObject.id, gameObject)
REPLAY.requesting = false REPLAY.requesting = false
REPLAY.log = replay.log REPLAY.log = replay.log
@ -329,8 +335,8 @@ export async function main(
await connect() await connect()
const TILE_DRAW_OFFSET = Game.getTileDrawOffset(gameId) const PIECE_DRAW_OFFSET = Game.getPieceDrawOffset(gameId)
const TILE_DRAW_SIZE = Game.getTileDrawSize(gameId) const PIECE_DRAW_SIZE = Game.getPieceDrawSize(gameId)
const PUZZLE_WIDTH = Game.getPuzzleWidth(gameId) const PUZZLE_WIDTH = Game.getPuzzleWidth(gameId)
const PUZZLE_HEIGHT = Game.getPuzzleHeight(gameId) const PUZZLE_HEIGHT = Game.getPuzzleHeight(gameId)
const TABLE_WIDTH = Game.getTableWidth(gameId) const TABLE_WIDTH = Game.getTableWidth(gameId)
@ -345,8 +351,8 @@ export async function main(
h: PUZZLE_HEIGHT, h: PUZZLE_HEIGHT,
} }
const PIECE_DIM = { const PIECE_DIM = {
w: TILE_DRAW_SIZE, w: PIECE_DRAW_SIZE,
h: TILE_DRAW_SIZE, h: PIECE_DRAW_SIZE,
} }
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(Game.getPuzzle(gameId)) const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(Game.getPuzzle(gameId))
@ -380,8 +386,8 @@ export async function main(
} }
updateTimerElements() updateTimerElements()
HUD.setPiecesDone(Game.getFinishedTileCount(gameId)) HUD.setPiecesDone(Game.getFinishedPiecesCount(gameId))
HUD.setPiecesTotal(Game.getTileCount(gameId)) HUD.setPiecesTotal(Game.getPieceCount(gameId))
const ts = TIME() const ts = TIME()
HUD.setActivePlayers(Game.getActivePlayers(gameId, ts)) HUD.setActivePlayers(Game.getActivePlayers(gameId, ts))
HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts)) HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts))
@ -470,8 +476,8 @@ export async function main(
} }
} break; } break;
case Protocol.CHANGE_TILE: { case Protocol.CHANGE_TILE: {
const t = Util.decodeTile(changeData) const t = Util.decodePiece(changeData)
Game.setTile(gameId, t.idx, t) Game.setPiece(gameId, t.idx, t)
RERENDER = true RERENDER = true
} break; } break;
case Protocol.CHANGE_DATA: { case Protocol.CHANGE_DATA: {
@ -494,8 +500,7 @@ export async function main(
if (REPLAY.logPointer + 1 >= REPLAY.log.length) { if (REPLAY.logPointer + 1 >= REPLAY.log.length) {
REPLAY.lastRealTs = realTs REPLAY.lastRealTs = realTs
REPLAY.requesting = true getNextReplayBatch(gameId)
getNextReplayBatch(gameId, REPLAY.logIdx, 10000)
return return
} }
@ -519,7 +524,7 @@ export async function main(
} }
const logEntry = REPLAY.log[nextIdx] const logEntry = REPLAY.log[nextIdx]
const nextTs = REPLAY.gameStartTs + logEntry[logEntry.length - 1] const nextTs: Timestamp = REPLAY.gameStartTs + logEntry[logEntry.length - 1]
if (nextTs > maxGameTs) { if (nextTs > maxGameTs) {
break break
} }
@ -546,7 +551,6 @@ export async function main(
RERENDER = true RERENDER = true
} }
REPLAY.logPointer = nextIdx REPLAY.logPointer = nextIdx
REPLAY.logIdx++
} while (true) } while (true)
REPLAY.lastRealTs = realTs REPLAY.lastRealTs = realTs
REPLAY.lastGameTs = maxGameTs REPLAY.lastGameTs = maxGameTs
@ -572,7 +576,7 @@ export async function main(
RERENDER = true RERENDER = true
viewport.move(diffX, diffY) viewport.move(diffX, diffY)
} else if (type === Protocol.INPUT_EV_MOUSE_MOVE) { } else if (type === Protocol.INPUT_EV_MOUSE_MOVE) {
if (_last_mouse_down && !Game.getFirstOwnedTile(gameId, clientId)) { if (_last_mouse_down && !Game.getFirstOwnedPiece(gameId, clientId)) {
// move the cam // move the cam
const pos = { x: evt[1], y: evt[2] } const pos = { x: evt[1], y: evt[2] }
const mouse = viewport.worldToViewport(pos) const mouse = viewport.worldToViewport(pos)
@ -692,7 +696,7 @@ export async function main(
// DRAW TILES // DRAW TILES
// --------------------------------------------------------------- // ---------------------------------------------------------------
const tiles = Game.getTilesSortedByZIndex(gameId) const tiles = Game.getPiecesSortedByZIndex(gameId)
if (window.DEBUG) Debug.checkpoint('get tiles done') if (window.DEBUG) Debug.checkpoint('get tiles done')
dim = viewport.worldDimToViewportRaw(PIECE_DIM) dim = viewport.worldDimToViewportRaw(PIECE_DIM)
@ -702,8 +706,8 @@ export async function main(
} }
bmp = bitmaps[tile.idx] bmp = bitmaps[tile.idx]
pos = viewport.worldToViewportRaw({ pos = viewport.worldToViewportRaw({
x: TILE_DRAW_OFFSET + tile.pos.x, x: PIECE_DRAW_OFFSET + tile.pos.x,
y: TILE_DRAW_OFFSET + tile.pos.y, y: PIECE_DRAW_OFFSET + tile.pos.y,
}) })
ctx.drawImage(bmp, ctx.drawImage(bmp,
0, 0, bmp.width, bmp.height, 0, 0, bmp.width, bmp.height,
@ -743,7 +747,7 @@ export async function main(
// --------------------------------------------------------------- // ---------------------------------------------------------------
HUD.setActivePlayers(Game.getActivePlayers(gameId, ts)) HUD.setActivePlayers(Game.getActivePlayers(gameId, ts))
HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts)) HUD.setIdlePlayers(Game.getIdlePlayers(gameId, ts))
HUD.setPiecesDone(Game.getFinishedTileCount(gameId)) HUD.setPiecesDone(Game.getFinishedPiecesCount(gameId))
if (window.DEBUG) Debug.checkpoint('HUD done') if (window.DEBUG) Debug.checkpoint('HUD done')
// --------------------------------------------------------------- // ---------------------------------------------------------------

View file

@ -1,18 +1,18 @@
import GameCommon, { ScoreMode } from './../common/GameCommon' import GameCommon, { Game, ScoreMode, Timestamp } from './../common/GameCommon'
import Util from './../common/Util' import Util from './../common/Util'
import { Rng } from './../common/Rng' import { Rng } from './../common/Rng'
import GameLog from './GameLog' import GameLog from './GameLog'
import { createPuzzle } from './Puzzle' import { createPuzzle, PuzzleCreationImageInfo } from './Puzzle'
import Protocol from './../common/Protocol' import Protocol from './../common/Protocol'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
async function createGameObject( async function createGameObject(
gameId: string, gameId: string,
targetTiles: number, targetTiles: number,
image: { file: string, url: string }, image: PuzzleCreationImageInfo,
ts: number, ts: number,
scoreMode: ScoreMode scoreMode: ScoreMode
) { ): Promise<Game> {
const seed = Util.hash(gameId + ' ' + ts) const seed = Util.hash(gameId + ' ' + ts)
const rng = new Rng(seed) const rng = new Rng(seed)
return { return {
@ -28,11 +28,17 @@ async function createGameObject(
async function createGame( async function createGame(
gameId: string, gameId: string,
targetTiles: number, targetTiles: number,
image: { file: string, url: string }, image: PuzzleCreationImageInfo,
ts: number, ts: number,
scoreMode: ScoreMode scoreMode: ScoreMode
): Promise<void> { ): Promise<void> {
const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode) const gameObject = await createGameObject(
gameId,
targetTiles,
image,
ts,
scoreMode
)
GameLog.create(gameId) GameLog.create(gameId)
GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode) GameLog.log(gameId, Protocol.LOG_HEADER, 1, targetTiles, image, ts, scoreMode)
@ -41,7 +47,7 @@ async function createGame(
GameStorage.setDirty(gameId) GameStorage.setDirty(gameId)
} }
function addPlayer(gameId: string, playerId: string, ts: number): void { function addPlayer(gameId: string, playerId: string, ts: Timestamp): void {
const idx = GameCommon.getPlayerIndexById(gameId, playerId) const idx = GameCommon.getPlayerIndexById(gameId, playerId)
const diff = ts - GameCommon.getStartTs(gameId) const diff = ts - GameCommon.getStartTs(gameId)
if (idx === -1) { if (idx === -1) {
@ -74,14 +80,4 @@ export default {
createGame, createGame,
addPlayer, addPlayer,
handleInput, handleInput,
getAllGames: GameCommon.getAllGames,
getActivePlayers: GameCommon.getActivePlayers,
getFinishedTileCount: GameCommon.getFinishedTileCount,
getImageUrl: GameCommon.getImageUrl,
getTileCount: GameCommon.getTileCount,
exists: GameCommon.exists,
playerExists: GameCommon.playerExists,
get: GameCommon.get,
getStartTs: GameCommon.getStartTs,
getFinishTs: GameCommon.getFinishTs,
} }

View file

@ -8,19 +8,19 @@ const log = logger('GameLog.js')
const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log` const filename = (gameId: string) => `${DATA_DIR}/log_${gameId}.log`
const create = (gameId: string) => { const create = (gameId: string): void => {
const file = filename(gameId) const file = filename(gameId)
if (!fs.existsSync(file)) { if (!fs.existsSync(file)) {
fs.appendFileSync(file, '') fs.appendFileSync(file, '')
} }
} }
const exists = (gameId: string) => { const exists = (gameId: string): boolean => {
const file = filename(gameId) const file = filename(gameId)
return fs.existsSync(file) return fs.existsSync(file)
} }
const _log = (gameId: string, ...args: Array<any>) => { const _log = (gameId: string, ...args: Array<any>): void => {
const file = filename(gameId) const file = filename(gameId)
if (!fs.existsSync(file)) { if (!fs.existsSync(file)) {
return return

View file

@ -41,7 +41,7 @@ function loadGame(gameId: string): void {
} }
if (typeof game.puzzle.data.finished === 'undefined') { if (typeof game.puzzle.data.finished === 'undefined') {
const unfinished = game.puzzle.tiles const unfinished = game.puzzle.tiles
.map(Util.decodeTile) .map(Util.decodePiece)
.find((t: Piece) => t.owner !== -1) .find((t: Piece) => t.owner !== -1)
game.puzzle.data.finished = unfinished ? 0 : Time.timestamp() game.puzzle.data.finished = unfinished ? 0 : Time.timestamp()
} }

View file

@ -2,7 +2,12 @@ import Util from './../common/Util'
import { Rng } from './../common/Rng' import { Rng } from './../common/Rng'
import Images from './Images' import Images from './Images'
import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle } from '../common/GameCommon' import { EncodedPiece, EncodedPieceShape, PieceShape, Puzzle } from '../common/GameCommon'
import { Point } from '../common/Geometry' import { Dim, Point } from '../common/Geometry'
export interface PuzzleCreationImageInfo {
file: string
url: string
}
interface PuzzleCreationInfo { interface PuzzleCreationInfo {
width: number width: number
@ -22,7 +27,7 @@ const TILE_SIZE = 64
async function createPuzzle( async function createPuzzle(
rng: Rng, rng: Rng,
targetTiles: number, targetTiles: number,
image: { file: string, url: string }, image: PuzzleCreationImageInfo,
ts: number ts: number
): Promise<Puzzle> { ): Promise<Puzzle> {
const imagePath = image.file const imagePath = image.file
@ -33,11 +38,7 @@ async function createPuzzle(
if (!dim.w || !dim.h) { if (!dim.w || !dim.h) {
throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]` throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]`
} }
const info: PuzzleCreationInfo = determinePuzzleInfo( const info: PuzzleCreationInfo = determinePuzzleInfo(dim, targetTiles)
dim.w,
dim.h,
targetTiles
)
let tiles = new Array(info.tiles) let tiles = new Array(info.tiles)
for (let i = 0; i < tiles.length; i++) { for (let i = 0; i < tiles.length; i++) {
@ -98,7 +99,7 @@ async function createPuzzle(
positions = rng.shuffle(positions) positions = rng.shuffle(positions)
const pieces: Array<EncodedPiece> = tiles.map(tile => { const pieces: Array<EncodedPiece> = tiles.map(tile => {
return Util.encodeTile({ return Util.encodePiece({
idx: tile.idx, // index of tile in the array idx: tile.idx, // index of tile in the array
group: 0, // if grouped with other tiles group: 0, // if grouped with other tiles
z: 0, // z index of the tile z: 0, // z index of the tile
@ -181,9 +182,12 @@ function determinePuzzleTileShapes(
return shapes.map(Util.encodeShape) return shapes.map(Util.encodeShape)
} }
const determineTilesXY = (w: number, h: number, targetTiles: number) => { const determineTilesXY = (
const w_ = w < h ? (w * h) : (w * w) dim: Dim,
const h_ = w < h ? (h * h) : (w * h) targetTiles: number
): { tilesX: number, tilesY: number } => {
const w_ = dim.w < dim.h ? (dim.w * dim.h) : (dim.w * dim.w)
const h_ = dim.w < dim.h ? (dim.h * dim.h) : (dim.w * dim.h)
let size = 0 let size = 0
let tiles = 0 let tiles = 0
do { do {
@ -198,11 +202,10 @@ const determineTilesXY = (w: number, h: number, targetTiles: number) => {
} }
const determinePuzzleInfo = ( const determinePuzzleInfo = (
w: number, dim: Dim,
h: number,
targetTiles: number targetTiles: number
): PuzzleCreationInfo => { ): PuzzleCreationInfo => {
const {tilesX, tilesY} = determineTilesXY(w, h, targetTiles) const {tilesX, tilesY} = determineTilesXY(dim, targetTiles)
const tiles = tilesX * tilesY const tiles = tilesX * tilesY
const tileSize = TILE_SIZE const tileSize = TILE_SIZE
const width = tilesX * tileSize const width = tilesX * tileSize

View file

@ -19,7 +19,7 @@ import {
PUBLIC_DIR, PUBLIC_DIR,
UPLOAD_DIR, UPLOAD_DIR,
} from './Dirs' } from './Dirs'
import { GameSettings, ScoreMode } from '../common/GameCommon' import GameCommon, { Game as GameType, GameSettings, ScoreMode } from '../common/GameCommon'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
import Db from './Db' import Db from './Db'
@ -81,7 +81,7 @@ app.get('/api/replay-data', async (req, res) => {
return return
} }
const log = await GameLog.get(gameId, offset, size) const log = await GameLog.get(gameId, offset, size)
let game = null let game: GameType|null = null
if (offset === 0) { if (offset === 0) {
// also need the game // also need the game
game = await Game.createGameObject( game = await Game.createGameObject(
@ -107,15 +107,15 @@ app.get('/api/newgame-data', (req, res) => {
app.get('/api/index-data', (req, res) => { app.get('/api/index-data', (req, res) => {
const ts = Time.timestamp() const ts = Time.timestamp()
const games = [ const games = [
...Game.getAllGames().map((game: any) => ({ ...GameCommon.getAllGames().map((game: any) => ({
id: game.id, id: game.id,
hasReplay: GameLog.exists(game.id), hasReplay: GameLog.exists(game.id),
started: Game.getStartTs(game.id), started: GameCommon.getStartTs(game.id),
finished: Game.getFinishTs(game.id), finished: GameCommon.getFinishTs(game.id),
tilesFinished: Game.getFinishedTileCount(game.id), tilesFinished: GameCommon.getFinishedPiecesCount(game.id),
tilesTotal: Game.getTileCount(game.id), tilesTotal: GameCommon.getPieceCount(game.id),
players: Game.getActivePlayers(game.id, ts).length, players: GameCommon.getActivePlayers(game.id, ts).length,
imageUrl: Game.getImageUrl(game.id), imageUrl: GameCommon.getImageUrl(game.id),
})), })),
] ]
@ -193,7 +193,7 @@ app.post('/newgame', bodyParser.json(), async (req, res) => {
const gameSettings = req.body as GameSettings const gameSettings = req.body as GameSettings
log.log(gameSettings) log.log(gameSettings)
const gameId = Util.uniqId() const gameId = Util.uniqId()
if (!Game.exists(gameId)) { if (!GameCommon.exists(gameId)) {
const ts = Time.timestamp() const ts = Time.timestamp()
await Game.createGame( await Game.createGame(
gameId, gameId,
@ -238,13 +238,13 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
const msgType = msg[0] const msgType = msg[0]
switch (msgType) { switch (msgType) {
case Protocol.EV_CLIENT_INIT: { case Protocol.EV_CLIENT_INIT: {
if (!Game.exists(gameId)) { if (!GameCommon.exists(gameId)) {
throw `[game ${gameId} does not exist... ]` throw `[game ${gameId} does not exist... ]`
} }
const ts = Time.timestamp() const ts = Time.timestamp()
Game.addPlayer(gameId, clientId, ts) Game.addPlayer(gameId, clientId, ts)
GameSockets.addSocket(gameId, socket) GameSockets.addSocket(gameId, socket)
const game = Game.get(gameId) const game: GameType = GameCommon.get(gameId)
notify( notify(
[Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [Protocol.EV_SERVER_INIT, Util.encodeGame(game)],
[socket] [socket]
@ -252,7 +252,7 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
} break } break
case Protocol.EV_CLIENT_EVENT: { case Protocol.EV_CLIENT_EVENT: {
if (!Game.exists(gameId)) { if (!GameCommon.exists(gameId)) {
throw `[game ${gameId} does not exist... ]` throw `[game ${gameId} does not exist... ]`
} }
const clientSeq = msg[1] const clientSeq = msg[1]
@ -260,7 +260,7 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
const ts = Time.timestamp() const ts = Time.timestamp()
let sendGame = false let sendGame = false
if (!Game.playerExists(gameId, clientId)) { if (!GameCommon.playerExists(gameId, clientId)) {
Game.addPlayer(gameId, clientId, ts) Game.addPlayer(gameId, clientId, ts)
sendGame = true sendGame = true
} }
@ -269,7 +269,7 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
sendGame = true sendGame = true
} }
if (sendGame) { if (sendGame) {
const game = Game.get(gameId) const game: GameType = GameCommon.get(gameId)
notify( notify(
[Protocol.EV_SERVER_INIT, Util.encodeGame(game)], [Protocol.EV_SERVER_INIT, Util.encodeGame(game)],
[socket] [socket]