some more type hinting etc

This commit is contained in:
Zutatensuppe 2021-05-17 02:32:33 +02:00
parent 432e1b6668
commit ee7804a594
18 changed files with 161 additions and 150 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

@ -2,7 +2,7 @@
<head> <head>
<title>🧩 jigsaw.hyottoko.club</title> <title>🧩 jigsaw.hyottoko.club</title>
<script type="module" crossorigin src="/assets/index.2f18bf13.js"></script> <script type="module" crossorigin src="/assets/index.85898c1b.js"></script>
<link rel="modulepreload" href="/assets/vendor.00b608ff.js"> <link rel="modulepreload" href="/assets/vendor.00b608ff.js">
<link rel="stylesheet" href="/assets/index.421011a7.css"> <link rel="stylesheet" href="/assets/index.421011a7.css">
</head> </head>

View file

@ -74,9 +74,6 @@ const logger = (...pre) => {
// get a unique id // get a unique id
const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2); const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2);
function encodeShape(data) { function encodeShape(data) {
if (typeof data === 'number') {
return data;
}
/* encoded in 1 byte: /* encoded in 1 byte:
00000000 00000000
^^ top ^^ top
@ -90,9 +87,6 @@ function encodeShape(data) {
| ((data.left + 1) << 6); | ((data.left + 1) << 6);
} }
function decodeShape(data) { function decodeShape(data) {
if (typeof data !== 'number') {
return data;
}
return { return {
top: (data >> 0 & 0b11) - 1, top: (data >> 0 & 0b11) - 1,
right: (data >> 2 & 0b11) - 1, right: (data >> 2 & 0b11) - 1,
@ -101,15 +95,9 @@ function decodeShape(data) {
}; };
} }
function encodeTile(data) { function encodeTile(data) {
if (Array.isArray(data)) {
return data;
}
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) { function decodeTile(data) {
if (!Array.isArray(data)) {
return data;
}
return { return {
idx: data[0], idx: data[0],
pos: { pos: {
@ -122,9 +110,6 @@ function decodeTile(data) {
}; };
} }
function encodePlayer(data) { function encodePlayer(data) {
if (Array.isArray(data)) {
return data;
}
return [ return [
data.id, data.id,
data.x, data.x,
@ -138,9 +123,6 @@ function encodePlayer(data) {
]; ];
} }
function decodePlayer(data) { function decodePlayer(data) {
if (!Array.isArray(data)) {
return data;
}
return { return {
id: data[0], id: data[0],
x: data[1], x: data[1],
@ -461,8 +443,17 @@ var Time = {
durationStr, durationStr,
}; };
const SCORE_MODE_FINAL = 0; var PieceEdge;
const SCORE_MODE_ANY = 1; (function (PieceEdge) {
PieceEdge[PieceEdge["Flat"] = 0] = "Flat";
PieceEdge[PieceEdge["Out"] = 1] = "Out";
PieceEdge[PieceEdge["In"] = -1] = "In";
})(PieceEdge || (PieceEdge = {}));
var ScoreMode;
(function (ScoreMode) {
ScoreMode[ScoreMode["FINAL"] = 0] = "FINAL";
ScoreMode[ScoreMode["ANY"] = 1] = "ANY";
})(ScoreMode || (ScoreMode = {}));
const IDLE_TIMEOUT_SEC = 30; const IDLE_TIMEOUT_SEC = 30;
// Map<gameId, Game> // Map<gameId, Game>
const GAMES = {}; const GAMES = {};
@ -502,11 +493,11 @@ function getPlayerIdByIndex(gameId, playerIndex) {
return null; return null;
} }
function getPlayer(gameId, playerId) { function getPlayer(gameId, playerId) {
let idx = getPlayerIndexById(gameId, playerId); const idx = getPlayerIndexById(gameId, playerId);
return Util.decodePlayer(GAMES[gameId].players[idx]); return Util.decodePlayer(GAMES[gameId].players[idx]);
} }
function setPlayer(gameId, playerId, player) { function setPlayer(gameId, playerId, player) {
let idx = getPlayerIndexById(gameId, playerId); const idx = getPlayerIndexById(gameId, playerId);
if (idx === -1) { if (idx === -1) {
GAMES[gameId].players.push(Util.encodePlayer(player)); GAMES[gameId].players.push(Util.encodePlayer(player));
} }
@ -580,7 +571,7 @@ function setImageUrl(gameId, imageUrl) {
GAMES[gameId].puzzle.info.imageUrl = imageUrl; GAMES[gameId].puzzle.info.imageUrl = imageUrl;
} }
function getScoreMode(gameId) { function getScoreMode(gameId) {
return GAMES[gameId].scoreMode || SCORE_MODE_FINAL; return GAMES[gameId].scoreMode || ScoreMode.FINAL;
} }
function isFinished(gameId) { function isFinished(gameId) {
return getFinishedTileCount(gameId) === getTileCount(gameId); return getFinishedTileCount(gameId) === getTileCount(gameId);
@ -601,6 +592,7 @@ function getTilesSortedByZIndex(gameId) {
function changePlayer(gameId, playerId, change) { function changePlayer(gameId, playerId, change) {
const player = getPlayer(gameId, playerId); const player = getPlayer(gameId, playerId);
for (let k of Object.keys(change)) { for (let k of Object.keys(change)) {
// @ts-ignore
player[k] = change[k]; player[k] = change[k];
} }
setPlayer(gameId, playerId, player); setPlayer(gameId, playerId, player);
@ -614,6 +606,7 @@ function changeData(gameId, change) {
function changeTile(gameId, tileIdx, change) { function changeTile(gameId, tileIdx, change) {
for (let k of Object.keys(change)) { for (let k of Object.keys(change)) {
const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]); const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]);
// @ts-ignore
tile[k] = change[k]; tile[k] = change[k];
GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile); GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile);
} }
@ -832,7 +825,7 @@ const getPlayerName = (gameId, playerId) => {
}; };
const getPlayerPoints = (gameId, playerId) => { const getPlayerPoints = (gameId, playerId) => {
const p = getPlayer(gameId, playerId); const p = getPlayer(gameId, playerId);
return p ? p.points : null; return p ? p.points : 0;
}; };
// determine if two tiles are grouped together // determine if two tiles are grouped together
const areGrouped = (gameId, tileIdx1, tileIdx2) => { const areGrouped = (gameId, tileIdx1, tileIdx2) => {
@ -1023,10 +1016,10 @@ function handleInput$1(gameId, playerId, input, ts) {
finishTiles(gameId, tileIdxs); finishTiles(gameId, tileIdxs);
_tileChanges(tileIdxs); _tileChanges(tileIdxs);
let points = getPlayerPoints(gameId, playerId); let points = getPlayerPoints(gameId, playerId);
if (getScoreMode(gameId) === SCORE_MODE_FINAL) { if (getScoreMode(gameId) === ScoreMode.FINAL) {
points += tileIdxs.length; points += tileIdxs.length;
} }
else if (getScoreMode(gameId) === SCORE_MODE_ANY) { else if (getScoreMode(gameId) === ScoreMode.ANY) {
points += 1; points += 1;
} }
else ; else ;
@ -1075,7 +1068,7 @@ function handleInput$1(gameId, playerId, input, ts) {
break; break;
} }
} }
if (snapped && getScoreMode(gameId) === SCORE_MODE_ANY) { if (snapped && getScoreMode(gameId) === ScoreMode.ANY) {
const points = getPlayerPoints(gameId, playerId) + 1; const points = getPlayerPoints(gameId, playerId) + 1;
changePlayer(gameId, playerId, { d, ts, points }); changePlayer(gameId, playerId, { d, ts, points });
_playerChange(); _playerChange();
@ -1150,8 +1143,6 @@ var GameCommon = {
getStartTs, getStartTs,
getFinishTs, getFinishTs,
handleInput: handleInput$1, handleInput: handleInput$1,
SCORE_MODE_FINAL,
SCORE_MODE_ANY,
}; };
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@ -1343,7 +1334,7 @@ async function createPuzzle(rng, targetTiles, image, ts) {
} }
// then shuffle the positions // then shuffle the positions
positions = rng.shuffle(positions); positions = rng.shuffle(positions);
tiles = tiles.map(tile => { const pieces = tiles.map(tile => {
return Util.encodeTile({ return Util.encodeTile({
idx: tile.idx, idx: tile.idx,
group: 0, group: 0,
@ -1362,7 +1353,7 @@ async function createPuzzle(rng, targetTiles, image, ts) {
// Complete puzzle object // Complete puzzle object
return { return {
// tiles array // tiles array
tiles, tiles: pieces,
// game data for puzzle, data changes during the game // game data for puzzle, data changes during the game
data: { data: {
// TODO: maybe calculate this each time? // TODO: maybe calculate this each time?
@ -1500,7 +1491,7 @@ function loadGame(gameId) {
puzzle: game.puzzle, puzzle: game.puzzle,
players: game.players, players: game.players,
evtInfos: {}, evtInfos: {},
scoreMode: game.scoreMode || GameCommon.SCORE_MODE_FINAL, scoreMode: game.scoreMode || ScoreMode.FINAL,
}; };
GameCommon.setGame(gameObject.id, gameObject); GameCommon.setGame(gameObject.id, gameObject);
} }
@ -1746,7 +1737,7 @@ wss.on('message', async ({ socket, data }) => {
throw `[gamelog ${gameId} does not exist... ]`; throw `[gamelog ${gameId} does not exist... ]`;
} }
const log = GameLog.get(gameId); const log = GameLog.get(gameId);
const game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || GameCommon.SCORE_MODE_FINAL); const game = await Game.createGameObject(gameId, log[0][2], log[0][3], log[0][4], log[0][5] || ScoreMode.FINAL);
notify([Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], [socket]); notify([Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], [socket]);
} }
break; break;

View file

@ -1,16 +1,12 @@
import Geometry from './Geometry' import Geometry, { Point, Rect } from './Geometry'
import Protocol from './Protocol' import Protocol from './Protocol'
import { Rng } from './Rng' import { Rng } from './Rng'
import Time from './Time' import Time from './Time'
import Util from './Util' import Util from './Util'
interface EncodedPlayer extends Array<any> {} export type EncodedPlayer = Array<any>
interface EncodedPiece extends Array<any> {} export type EncodedPiece = Array<any>
export type EncodedPieceShape = number
interface Point {
x: number
y: number
}
interface GameRng { interface GameRng {
obj: Rng obj: Rng
@ -22,7 +18,7 @@ interface Game {
players: Array<EncodedPlayer> players: Array<EncodedPlayer>
puzzle: Puzzle puzzle: Puzzle
evtInfos: Record<string, EvtInfo> evtInfos: Record<string, EvtInfo>
scoreMode?: number scoreMode?: ScoreMode
rng: GameRng rng: GameRng
} }
@ -44,15 +40,24 @@ interface PuzzleTable {
height: number height: number
} }
enum PieceEdge {
Flat = 0,
Out = 1,
In = -1,
}
export interface PieceShape { export interface PieceShape {
top: 0|1|-1 top: PieceEdge
bottom: 0|1|-1 bottom: PieceEdge
left: 0|1|-1 left: PieceEdge
right: 0|1|-1 right: PieceEdge
} }
export interface Piece { export interface Piece {
owner: string|number owner: string|number
idx: number
pos: Point
z: number
group: number
} }
export interface PuzzleInfo { export interface PuzzleInfo {
@ -72,8 +77,7 @@ export interface PuzzleInfo {
tilesX: number tilesX: number
tilesY: number tilesY: number
// TODO: ts type Array<PieceShape> shapes: Array<EncodedPieceShape>
shapes: Array<any>
} }
export interface Player { export interface Player {
@ -93,8 +97,10 @@ interface EvtInfo {
_last_mouse_down: Point|null _last_mouse_down: Point|null
} }
const SCORE_MODE_FINAL = 0 export enum ScoreMode {
const SCORE_MODE_ANY = 1 FINAL = 0,
ANY = 1,
}
const IDLE_TIMEOUT_SEC = 30 const IDLE_TIMEOUT_SEC = 30
@ -134,20 +140,20 @@ function getPlayerIndexById(gameId: string, playerId: string): number {
return -1 return -1
} }
function getPlayerIdByIndex(gameId: string, playerIndex: number) { function getPlayerIdByIndex(gameId: string, playerIndex: number): string|null {
if (GAMES[gameId].players.length > playerIndex) { if (GAMES[gameId].players.length > playerIndex) {
return Util.decodePlayer(GAMES[gameId].players[playerIndex]).id return Util.decodePlayer(GAMES[gameId].players[playerIndex]).id
} }
return null return null
} }
function getPlayer(gameId: string, playerId: string) { function getPlayer(gameId: string, playerId: string): Player {
let idx = getPlayerIndexById(gameId, playerId) const idx = getPlayerIndexById(gameId, playerId)
return Util.decodePlayer(GAMES[gameId].players[idx]) return Util.decodePlayer(GAMES[gameId].players[idx])
} }
function setPlayer(gameId: string, playerId: string, player: Player) { function setPlayer(gameId: string, playerId: string, player: Player) {
let idx = getPlayerIndexById(gameId, playerId) const idx = getPlayerIndexById(gameId, playerId)
if (idx === -1) { if (idx === -1) {
GAMES[gameId].players.push(Util.encodePlayer(player)) GAMES[gameId].players.push(Util.encodePlayer(player))
} else { } else {
@ -168,17 +174,17 @@ function playerExists(gameId: string, playerId: string) {
return idx !== -1 return idx !== -1
} }
function getActivePlayers(gameId: string, ts: number) { function getActivePlayers(gameId: string, ts: number): Array<Player> {
const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC
return getAllPlayers(gameId).filter((p: Player) => p.ts >= minTs) return getAllPlayers(gameId).filter((p: Player) => p.ts >= minTs)
} }
function getIdlePlayers(gameId: string, ts: number) { function getIdlePlayers(gameId: string, ts: number): Array<Player> {
const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC const minTs = ts - IDLE_TIMEOUT_SEC * Time.SEC
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) { function addPlayer(gameId: string, playerId: string, ts: number): void {
if (!playerExists(gameId, playerId)) { if (!playerExists(gameId, playerId)) {
setPlayer(gameId, playerId, __createPlayerObject(playerId, ts)) setPlayer(gameId, playerId, __createPlayerObject(playerId, ts))
} else { } else {
@ -186,7 +192,7 @@ function addPlayer(gameId: string, playerId: string, ts: number) {
} }
} }
function getEvtInfo(gameId: string, playerId: string) { function getEvtInfo(gameId: string, playerId: string): EvtInfo {
if (playerId in GAMES[gameId].evtInfos) { if (playerId in GAMES[gameId].evtInfos) {
return GAMES[gameId].evtInfos[playerId] return GAMES[gameId].evtInfos[playerId]
} }
@ -211,7 +217,7 @@ function getAllGames(): Array<Game> {
}) })
} }
function getAllPlayers(gameId: string) { function getAllPlayers(gameId: string): Array<Player> {
return GAMES[gameId] return GAMES[gameId]
? GAMES[gameId].players.map(Util.decodePlayer) ? GAMES[gameId].players.map(Util.decodePlayer)
: [] : []
@ -221,11 +227,11 @@ function get(gameId: string) {
return GAMES[gameId] return GAMES[gameId]
} }
function getTileCount(gameId: string) { function getTileCount(gameId: string): number {
return GAMES[gameId].puzzle.tiles.length return GAMES[gameId].puzzle.tiles.length
} }
function getImageUrl(gameId: string) { function getImageUrl(gameId: string): string {
return GAMES[gameId].puzzle.info.imageUrl return GAMES[gameId].puzzle.info.imageUrl
} }
@ -233,8 +239,8 @@ function setImageUrl(gameId: string, imageUrl: string) {
GAMES[gameId].puzzle.info.imageUrl = imageUrl GAMES[gameId].puzzle.info.imageUrl = imageUrl
} }
function getScoreMode(gameId: string) { function getScoreMode(gameId: string): ScoreMode {
return GAMES[gameId].scoreMode || SCORE_MODE_FINAL return GAMES[gameId].scoreMode || ScoreMode.FINAL
} }
function isFinished(gameId: string) { function isFinished(gameId: string) {
@ -259,6 +265,7 @@ function getTilesSortedByZIndex(gameId: string) {
function changePlayer(gameId: string, playerId: string, change: any) { function changePlayer(gameId: string, playerId: string, change: any) {
const player = getPlayer(gameId, playerId) const player = getPlayer(gameId, playerId)
for (let k of Object.keys(change)) { for (let k of Object.keys(change)) {
// @ts-ignore
player[k] = change[k] player[k] = change[k]
} }
setPlayer(gameId, playerId, player) setPlayer(gameId, playerId, player)
@ -274,12 +281,13 @@ function changeData(gameId: string, change: any) {
function changeTile(gameId: string, tileIdx: number, change: any) { function changeTile(gameId: string, tileIdx: number, change: any) {
for (let k of Object.keys(change)) { for (let k of Object.keys(change)) {
const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) const tile = Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx])
// @ts-ignore
tile[k] = change[k] tile[k] = change[k]
GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile) GAMES[gameId].puzzle.tiles[tileIdx] = Util.encodeTile(tile)
} }
} }
const getTile = (gameId: string, tileIdx: number) => { const getTile = (gameId: string, tileIdx: number): Piece => {
return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx]) return Util.decodeTile(GAMES[gameId].puzzle.tiles[tileIdx])
} }
@ -500,7 +508,7 @@ const freeTileIdxByPos = (gameId: string, pos: Point) => {
continue continue
} }
const collisionRect = { const collisionRect: Rect = {
x: tile.pos.x, x: tile.pos.x,
y: tile.pos.y, y: tile.pos.y,
w: info.tileSize, w: info.tileSize,
@ -531,9 +539,9 @@ const getPlayerName = (gameId: string, playerId: string) => {
return p ? p.name : null return p ? p.name : null
} }
const getPlayerPoints = (gameId: string, playerId: string) => { const getPlayerPoints = (gameId: string, playerId: string): number => {
const p = getPlayer(gameId, playerId) const p = getPlayer(gameId, playerId)
return p ? p.points : null return p ? p.points : 0
} }
// determine if two tiles are grouped together // determine if two tiles are grouped together
@ -746,13 +754,13 @@ function handleInput(gameId: string, playerId: string, input: any, ts: number) {
_tileChanges(tileIdxs) _tileChanges(tileIdxs)
let points = getPlayerPoints(gameId, playerId) let points = getPlayerPoints(gameId, playerId)
if (getScoreMode(gameId) === SCORE_MODE_FINAL) { if (getScoreMode(gameId) === ScoreMode.FINAL) {
points += tileIdxs.length points += tileIdxs.length
} else if (getScoreMode(gameId) === SCORE_MODE_ANY) { } else if (getScoreMode(gameId) === ScoreMode.ANY) {
points += 1 points += 1
} else { } else {
// no score mode... should never occur, because there is a // no score mode... should never occur, because there is a
// fallback to SCORE_MODE_FINAL in getScoreMode function // fallback to ScoreMode.FINAL in getScoreMode function
} }
changePlayer(gameId, playerId, { d, ts, points }) changePlayer(gameId, playerId, { d, ts, points })
_playerChange() _playerChange()
@ -809,7 +817,7 @@ function handleInput(gameId: string, playerId: string, input: any, ts: number) {
break break
} }
} }
if (snapped && getScoreMode(gameId) === SCORE_MODE_ANY) { if (snapped && getScoreMode(gameId) === ScoreMode.ANY) {
const points = getPlayerPoints(gameId, playerId) + 1 const points = getPlayerPoints(gameId, playerId) + 1
changePlayer(gameId, playerId, { d, ts, points }) changePlayer(gameId, playerId, { d, ts, points })
_playerChange() _playerChange()
@ -881,6 +889,4 @@ export default {
getStartTs, getStartTs,
getFinishTs, getFinishTs,
handleInput, handleInput,
SCORE_MODE_FINAL,
SCORE_MODE_ANY,
} }

View file

@ -1,14 +1,18 @@
interface Point { export interface Point {
x: number x: number
y: number y: number
} }
interface Rect { export interface Rect {
x: number x: number
y: number y: number
w: number w: number
h: number h: number
} }
export interface Dim {
w: number
h: number
}
function pointSub(a: Point, b: Point): Point { function pointSub(a: Point, b: Point): Point {
return { x: a.x - b.x, y: a.y - b.y } return { x: a.x - b.x, y: a.y - b.y }

View file

@ -4,7 +4,7 @@ const MIN = SEC * 60
const HOUR = MIN * 60 const HOUR = MIN * 60
const DAY = HOUR * 24 const DAY = HOUR * 24
export const timestamp = () => { export const timestamp = (): number => {
const d = new Date(); const d = new Date();
return Date.UTC( return Date.UTC(
d.getUTCFullYear(), d.getUTCFullYear(),
@ -17,7 +17,7 @@ export const timestamp = () => {
) )
} }
export const durationStr = (duration: number) => { export const durationStr = (duration: number): string => {
const d = Math.floor(duration / DAY) const d = Math.floor(duration / DAY)
duration = duration % DAY duration = duration % DAY

View file

@ -1,3 +1,5 @@
import { EncodedPiece, EncodedPieceShape, EncodedPlayer, Piece, PieceShape, Player } from './GameCommon'
import { Point } from './Geometry'
import { Rng } from './Rng' import { Rng } from './Rng'
@ -27,10 +29,7 @@ export const logger = (...pre: Array<any>) => {
// get a unique id // get a unique id
export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2) export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2)
function encodeShape(data: any): number { function encodeShape(data: PieceShape): EncodedPieceShape {
if (typeof data === 'number') {
return data
}
/* encoded in 1 byte: /* encoded in 1 byte:
00000000 00000000
^^ top ^^ top
@ -44,10 +43,7 @@ function encodeShape(data: any): number {
| ((data.left + 1) << 6) | ((data.left + 1) << 6)
} }
function decodeShape(data: any) { function decodeShape(data: EncodedPieceShape): PieceShape {
if (typeof data !== 'number') {
return data
}
return { return {
top: (data >> 0 & 0b11) - 1, top: (data >> 0 & 0b11) - 1,
right: (data >> 2 & 0b11) - 1, right: (data >> 2 & 0b11) - 1,
@ -56,17 +52,11 @@ function decodeShape(data: any) {
} }
} }
function encodeTile(data: any): Array<any> { function encodeTile(data: Piece): EncodedPiece {
if (Array.isArray(data)) {
return data
}
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: any) { function decodeTile(data: EncodedPiece): Piece {
if (!Array.isArray(data)) {
return data
}
return { return {
idx: data[0], idx: data[0],
pos: { pos: {
@ -79,10 +69,7 @@ function decodeTile(data: any) {
} }
} }
function encodePlayer(data: any): Array<any> { function encodePlayer(data: Player): EncodedPlayer {
if (Array.isArray(data)) {
return data
}
return [ return [
data.id, data.id,
data.x, data.x,
@ -96,10 +83,7 @@ function encodePlayer(data: any): Array<any> {
] ]
} }
function decodePlayer(data: any) { function decodePlayer(data: EncodedPlayer): Player {
if (!Array.isArray(data)) {
return data
}
return { return {
id: data[0], id: data[0],
x: data[1], x: data[1],
@ -145,7 +129,7 @@ function decodeGame(data: any) {
} }
} }
function coordByTileIdx(info: any, tileIdx: number) { function coordByTileIdx(info: any, tileIdx: number): Point {
const wTiles = info.width / info.tileSize const wTiles = info.width / info.tileSize
return { return {
x: tileIdx % wTiles, x: tileIdx % wTiles,

View file

@ -1,16 +1,10 @@
import { Dim, Point } from "../common/Geometry"
const MIN_ZOOM = .1 const MIN_ZOOM = .1
const MAX_ZOOM = 6 const MAX_ZOOM = 6
const ZOOM_STEP = .05 const ZOOM_STEP = .05
type ZOOM_DIR = 'in'|'out' type ZOOM_DIR = 'in'|'out'
interface Point {
x: number
y: number
}
interface Dim {
w: number
h: number
}
export default function Camera () { export default function Camera () {
let x = 0 let x = 0

View file

@ -17,15 +17,23 @@ async function loadImageToBitmap(imagePath: string): Promise<ImageBitmap> {
}) })
} }
async function resizeBitmap (bitmap: ImageBitmap, width: number, height: number): Promise<ImageBitmap> { async function resizeBitmap (
bitmap: ImageBitmap,
width: number,
height: number
): Promise<ImageBitmap> {
const c = createCanvas(width, height) const c = createCanvas(width, height)
const ctx = c.getContext('2d') as CanvasRenderingContext2D const ctx = c.getContext('2d') as CanvasRenderingContext2D
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height) ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height)
return await createImageBitmap(c) return await createImageBitmap(c)
} }
async function colorize(image: ImageBitmap, mask: ImageBitmap, color: string): Promise<ImageBitmap> { async function colorize(
const c = createCanvas(image.width, image.height) bitmap: ImageBitmap,
mask: ImageBitmap,
color: string
): Promise<ImageBitmap> {
const c = createCanvas(bitmap.width, bitmap.height)
const ctx = c.getContext('2d') as CanvasRenderingContext2D const ctx = c.getContext('2d') as CanvasRenderingContext2D
ctx.save() ctx.save()
ctx.drawImage(mask, 0, 0) ctx.drawImage(mask, 0, 0)
@ -35,7 +43,7 @@ async function colorize(image: ImageBitmap, mask: ImageBitmap, color: string): P
ctx.restore() ctx.restore()
ctx.save() ctx.save()
ctx.globalCompositeOperation = "destination-over" ctx.globalCompositeOperation = "destination-over"
ctx.drawImage(image, 0, 0) ctx.drawImage(bitmap, 0, 0)
ctx.restore() ctx.restore()
return await createImageBitmap(c) return await createImageBitmap(c)
} }

View file

@ -1,13 +1,17 @@
"use strict" "use strict"
import Geometry from '../common/Geometry' import Geometry, { Rect } from '../common/Geometry'
import Graphics from './Graphics' import Graphics from './Graphics'
import Util, { logger } from './../common/Util' import Util, { logger } from './../common/Util'
import { Puzzle, PuzzleInfo, PieceShape } from './../common/GameCommon' import { Puzzle, PuzzleInfo, PieceShape } from './../common/GameCommon'
const log = logger('PuzzleGraphics.js') const log = logger('PuzzleGraphics.js')
async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array<any>, info: PuzzleInfo) { async function createPuzzleTileBitmaps(
img: ImageBitmap,
tiles: Array<any>,
info: PuzzleInfo
): Promise<Array<ImageBitmap>> {
log.log('start createPuzzleTileBitmaps') log.log('start createPuzzleTileBitmaps')
var tileSize = info.tileSize var tileSize = info.tileSize
var tileMarginWidth = info.tileMarginWidth var tileMarginWidth = info.tileMarginWidth
@ -23,7 +27,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array<any>, info
63, 5, 65, 15, 100, 0 63, 5, 65, 15, 100, 0
]; ];
const bitmaps = new Array(tiles.length) const bitmaps: Array<ImageBitmap> = new Array(tiles.length)
const paths: Record<string, Path2D> = {} const paths: Record<string, Path2D> = {}
function pathForShape(shape: PieceShape) { function pathForShape(shape: PieceShape) {
@ -89,7 +93,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array<any>, info
const c2 = Graphics.createCanvas(tileDrawSize, tileDrawSize) const c2 = Graphics.createCanvas(tileDrawSize, tileDrawSize)
const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D
for (let t of tiles) { for (const t of tiles) {
const tile = Util.decodeTile(t) const tile = Util.decodeTile(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]))
@ -198,7 +202,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array<any>, info
return bitmaps return bitmaps
} }
function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number) { function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number): Rect {
const c = Util.coordByTileIdx(puzzleInfo, idx) const c = Util.coordByTileIdx(puzzleInfo, idx)
return { return {
x: c.x * puzzleInfo.tileSize, x: c.x * puzzleInfo.tileSize,
@ -208,7 +212,7 @@ function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number) {
} }
} }
async function loadPuzzleBitmaps(puzzle: Puzzle) { async function loadPuzzleBitmaps(puzzle: Puzzle): Promise<Array<ImageBitmap>> {
// load bitmap, to determine the original size of the image // load bitmap, to determine the original size of the image
const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl) const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl)

View file

@ -44,7 +44,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
import GameCommon from './../../common/GameCommon' import { ScoreMode } from './../../common/GameCommon'
import Upload from './../components/Upload.vue' import Upload from './../components/Upload.vue'
import ImageTeaser from './../components/ImageTeaser.vue' import ImageTeaser from './../components/ImageTeaser.vue'
@ -64,7 +64,7 @@ export default defineComponent({
return { return {
tiles: 1000, tiles: 1000,
image: '', image: '',
scoreMode: GameCommon.SCORE_MODE_ANY, scoreMode: ScoreMode.ANY,
} }
}, },
methods: { methods: {

View file

@ -11,6 +11,7 @@ import Game, { Player, Piece } 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'
declare global { declare global {
interface Window { interface Window {
@ -34,10 +35,6 @@ export const MODE_REPLAY = 'replay'
let PIECE_VIEW_FIXED = true let PIECE_VIEW_FIXED = true
let PIECE_VIEW_LOOSE = true let PIECE_VIEW_LOOSE = true
interface Point {
x: number
y: number
}
interface Hud { interface Hud {
setActivePlayers: (v: Array<any>) => void setActivePlayers: (v: Array<any>) => void
setIdlePlayers: (v: Array<any>) => void setIdlePlayers: (v: Array<any>) => void
@ -474,10 +471,16 @@ export async function main(
RERENDER = true RERENDER = true
} else if (entryWithTs[0] === Protocol.LOG_UPDATE_PLAYER) { } else if (entryWithTs[0] === Protocol.LOG_UPDATE_PLAYER) {
const playerId = Game.getPlayerIdByIndex(gameId, entryWithTs[1]) const playerId = Game.getPlayerIdByIndex(gameId, entryWithTs[1])
if (!playerId) {
throw '[ 2021-05-17 player not found (update player) ]'
}
Game.addPlayer(gameId, playerId, nextTs) Game.addPlayer(gameId, playerId, nextTs)
RERENDER = true RERENDER = true
} else if (entryWithTs[0] === Protocol.LOG_HANDLE_INPUT) { } else if (entryWithTs[0] === Protocol.LOG_HANDLE_INPUT) {
const playerId = Game.getPlayerIdByIndex(gameId, entryWithTs[1]) const playerId = Game.getPlayerIdByIndex(gameId, entryWithTs[1])
if (!playerId) {
throw '[ 2021-05-17 player not found (handle input) ]'
}
const input = entryWithTs[2] const input = entryWithTs[2]
Game.handleInput(gameId, playerId, input, nextTs) Game.handleInput(gameId, playerId, input, nextTs)
RERENDER = true RERENDER = true
@ -598,9 +601,9 @@ export async function main(
const ts = TIME() const ts = TIME()
let pos let pos: Point
let dim let dim: Dim
let bmp let bmp: ImageBitmap
if (window.DEBUG) Debug.checkpoint_start(0) if (window.DEBUG) Debug.checkpoint_start(0)

View file

@ -29,7 +29,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue' import { defineComponent } from 'vue'
import Scores from './../components/Scores.vue' import Scores from './../components/Scores.vue'
import PuzzleStatus from './../components/PuzzleStatus.vue' import PuzzleStatus from './../components/PuzzleStatus.vue'
@ -39,6 +39,7 @@ import ConnectionOverlay from './../components/ConnectionOverlay.vue'
import HelpOverlay from './../components/HelpOverlay.vue' import HelpOverlay from './../components/HelpOverlay.vue'
import { main, MODE_PLAY } from './../game' import { main, MODE_PLAY } from './../game'
import { Player } from '../../common/GameCommon'
export default defineComponent({ export default defineComponent({
name: 'game', name: 'game',
@ -52,9 +53,8 @@ export default defineComponent({
}, },
data() { data() {
return { return {
// TODO: ts Array<Player> type activePlayers: [] as Array<Player>,
activePlayers: [] as PropType<Array<any>>, idlePlayers: [] as Array<Player>,
idlePlayers: [] as PropType<Array<any>>,
finished: false, finished: false,
duration: 0, duration: 0,
@ -103,8 +103,8 @@ export default defineComponent({
MODE_PLAY, MODE_PLAY,
this.$el, this.$el,
{ {
setActivePlayers: (v: Array<any>) => { this.activePlayers = v }, setActivePlayers: (v: Array<Player>) => { this.activePlayers = v },
setIdlePlayers: (v: Array<any>) => { this.idlePlayers = v }, setIdlePlayers: (v: Array<Player>) => { this.idlePlayers = v },
setFinished: (v: boolean) => { this.finished = v }, setFinished: (v: boolean) => { this.finished = v },
setDuration: (v: number) => { this.duration = v }, setDuration: (v: number) => { this.duration = v },
setPiecesDone: (v: number) => { this.piecesDone = v }, setPiecesDone: (v: number) => { this.piecesDone = v },

View file

@ -1,4 +1,4 @@
import GameCommon from './../common/GameCommon' import GameCommon, { ScoreMode } 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'
@ -6,7 +6,13 @@ import { createPuzzle } from './Puzzle'
import Protocol from './../common/Protocol' import Protocol from './../common/Protocol'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
async function createGameObject(gameId: string, targetTiles: number, image: { file: string, url: string }, ts: number, scoreMode: number) { async function createGameObject(
gameId: string,
targetTiles: number,
image: { file: string, url: string },
ts: number,
scoreMode: ScoreMode
) {
const seed = Util.hash(gameId + ' ' + ts) const seed = Util.hash(gameId + ' ' + ts)
const rng = new Rng(seed) const rng = new Rng(seed)
return { return {
@ -19,7 +25,13 @@ async function createGameObject(gameId: string, targetTiles: number, image: { fi
} }
} }
async function createGame(gameId: string, targetTiles: number, image: { file: string, url: string }, ts: number, scoreMode: number) { async function createGame(
gameId: string,
targetTiles: number,
image: { file: string, url: string },
ts: number,
scoreMode: ScoreMode
) {
const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode) const gameObject = await createGameObject(gameId, targetTiles, image, ts, scoreMode)
GameLog.create(gameId) GameLog.create(gameId)

View file

@ -1,5 +1,5 @@
import fs from 'fs' import fs from 'fs'
import GameCommon from './../common/GameCommon' import GameCommon, { ScoreMode } from './../common/GameCommon'
import Util, { logger } from './../common/Util' import Util, { logger } from './../common/Util'
import { Rng } from './../common/Rng' import { Rng } from './../common/Rng'
import { DATA_DIR } from './Dirs' import { DATA_DIR } from './Dirs'
@ -55,7 +55,7 @@ function loadGame(gameId: string): void {
puzzle: game.puzzle, puzzle: game.puzzle,
players: game.players, players: game.players,
evtInfos: {}, evtInfos: {},
scoreMode: game.scoreMode || GameCommon.SCORE_MODE_FINAL, scoreMode: game.scoreMode || ScoreMode.FINAL,
} }
GameCommon.setGame(gameObject.id, gameObject) GameCommon.setGame(gameObject.id, gameObject)
} }

View file

@ -1,6 +1,7 @@
import Util from './../common/Util' 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 } from '../common/GameCommon'
interface PuzzleInfo { interface PuzzleInfo {
width: number width: number
@ -31,7 +32,7 @@ async function createPuzzle(
if (!dim || !dim.width || !dim.height) { if (!dim || !dim.width || !dim.height) {
throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]` throw `[ 2021-05-16 invalid dimension for path ${imagePath} ]`
} }
const info = determinePuzzleInfo(dim.width, dim.height, targetTiles) const info: PuzzleInfo = determinePuzzleInfo(dim.width, dim.height, 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++) {
@ -91,7 +92,7 @@ async function createPuzzle(
// then shuffle the positions // then shuffle the positions
positions = rng.shuffle(positions) positions = rng.shuffle(positions)
tiles = tiles.map(tile => { const pieces: Array<EncodedPiece> = tiles.map(tile => {
return Util.encodeTile({ return Util.encodeTile({
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
@ -113,7 +114,7 @@ async function createPuzzle(
// Complete puzzle object // Complete puzzle object
return { return {
// tiles array // tiles array
tiles, tiles: pieces,
// game data for puzzle, data changes during the game // game data for puzzle, data changes during the game
data: { data: {
// TODO: maybe calculate this each time? // TODO: maybe calculate this each time?
@ -159,10 +160,10 @@ async function createPuzzle(
function determinePuzzleTileShapes( function determinePuzzleTileShapes(
rng: Rng, rng: Rng,
info: PuzzleInfo info: PuzzleInfo
) { ): Array<EncodedPieceShape> {
const tabs = [-1, 1] const tabs = [-1, 1]
const shapes = new Array(info.tiles) const shapes: Array<PieceShape> = new Array(info.tiles)
for (let i = 0; i < info.tiles; i++) { for (let i = 0; i < info.tiles; i++) {
let coord = Util.coordByTileIdx(info, i) let coord = Util.coordByTileIdx(info, i)
shapes[i] = { shapes[i] = {
@ -191,7 +192,11 @@ const determineTilesXY = (w: number, h: number, targetTiles: number) => {
} }
} }
const determinePuzzleInfo = (w: number, h: number, targetTiles: number) => { const determinePuzzleInfo = (
w: number,
h: number,
targetTiles: number
): PuzzleInfo => {
const {tilesX, tilesY} = determineTilesXY(w, h, targetTiles) const {tilesX, tilesY} = determineTilesXY(w, h, targetTiles)
const tiles = tilesX * tilesY const tiles = tilesX * tilesY
const tileSize = TILE_SIZE const tileSize = TILE_SIZE

View file

@ -13,7 +13,7 @@ import GameSockets from './GameSockets'
import Time from './../common/Time' import Time from './../common/Time'
import Images from './Images' import Images from './Images'
import { UPLOAD_DIR, UPLOAD_URL, PUBLIC_DIR } from './Dirs' import { UPLOAD_DIR, UPLOAD_URL, PUBLIC_DIR } from './Dirs'
import GameCommon from '../common/GameCommon' import GameCommon, { ScoreMode } from '../common/GameCommon'
import GameStorage from './GameStorage' import GameStorage from './GameStorage'
let configFile = '' let configFile = ''
@ -158,7 +158,7 @@ wss.on('message', async ({socket, data} : { socket: WebSocket, data: any }) => {
log[0][2], log[0][2],
log[0][3], log[0][3],
log[0][4], log[0][4],
log[0][5] || GameCommon.SCORE_MODE_FINAL log[0][5] || ScoreMode.FINAL
) )
notify( notify(
[Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log], [Protocol.EV_SERVER_INIT_REPLAY, Util.encodeGame(game), log],