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

View file

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

View file

@ -1,14 +1,18 @@
interface Point {
export interface Point {
x: number
y: number
}
interface Rect {
export interface Rect {
x: number
y: number
w: number
h: number
}
export interface Dim {
w: number
h: number
}
function pointSub(a: Point, b: Point): Point {
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 DAY = HOUR * 24
export const timestamp = () => {
export const timestamp = (): number => {
const d = new Date();
return Date.UTC(
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)
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'
@ -27,10 +29,7 @@ export const logger = (...pre: Array<any>) => {
// get a unique id
export const uniqId = () => Date.now().toString(36) + Math.random().toString(36).substring(2)
function encodeShape(data: any): number {
if (typeof data === 'number') {
return data
}
function encodeShape(data: PieceShape): EncodedPieceShape {
/* encoded in 1 byte:
00000000
^^ top
@ -44,10 +43,7 @@ function encodeShape(data: any): number {
| ((data.left + 1) << 6)
}
function decodeShape(data: any) {
if (typeof data !== 'number') {
return data
}
function decodeShape(data: EncodedPieceShape): PieceShape {
return {
top: (data >> 0 & 0b11) - 1,
right: (data >> 2 & 0b11) - 1,
@ -56,17 +52,11 @@ function decodeShape(data: any) {
}
}
function encodeTile(data: any): Array<any> {
if (Array.isArray(data)) {
return data
}
function encodeTile(data: Piece): EncodedPiece {
return [data.idx, data.pos.x, data.pos.y, data.z, data.owner, data.group]
}
function decodeTile(data: any) {
if (!Array.isArray(data)) {
return data
}
function decodeTile(data: EncodedPiece): Piece {
return {
idx: data[0],
pos: {
@ -79,10 +69,7 @@ function decodeTile(data: any) {
}
}
function encodePlayer(data: any): Array<any> {
if (Array.isArray(data)) {
return data
}
function encodePlayer(data: Player): EncodedPlayer {
return [
data.id,
data.x,
@ -96,10 +83,7 @@ function encodePlayer(data: any): Array<any> {
]
}
function decodePlayer(data: any) {
if (!Array.isArray(data)) {
return data
}
function decodePlayer(data: EncodedPlayer): Player {
return {
id: data[0],
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
return {
x: tileIdx % wTiles,

View file

@ -1,16 +1,10 @@
import { Dim, Point } from "../common/Geometry"
const MIN_ZOOM = .1
const MAX_ZOOM = 6
const ZOOM_STEP = .05
type ZOOM_DIR = 'in'|'out'
interface Point {
x: number
y: number
}
interface Dim {
w: number
h: number
}
export default function Camera () {
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 ctx = c.getContext('2d') as CanvasRenderingContext2D
ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height)
return await createImageBitmap(c)
}
async function colorize(image: ImageBitmap, mask: ImageBitmap, color: string): Promise<ImageBitmap> {
const c = createCanvas(image.width, image.height)
async function colorize(
bitmap: ImageBitmap,
mask: ImageBitmap,
color: string
): Promise<ImageBitmap> {
const c = createCanvas(bitmap.width, bitmap.height)
const ctx = c.getContext('2d') as CanvasRenderingContext2D
ctx.save()
ctx.drawImage(mask, 0, 0)
@ -35,7 +43,7 @@ async function colorize(image: ImageBitmap, mask: ImageBitmap, color: string): P
ctx.restore()
ctx.save()
ctx.globalCompositeOperation = "destination-over"
ctx.drawImage(image, 0, 0)
ctx.drawImage(bitmap, 0, 0)
ctx.restore()
return await createImageBitmap(c)
}

View file

@ -1,13 +1,17 @@
"use strict"
import Geometry from '../common/Geometry'
import Geometry, { Rect } from '../common/Geometry'
import Graphics from './Graphics'
import Util, { logger } from './../common/Util'
import { Puzzle, PuzzleInfo, PieceShape } from './../common/GameCommon'
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')
var tileSize = info.tileSize
var tileMarginWidth = info.tileMarginWidth
@ -23,7 +27,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array<any>, info
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> = {}
function pathForShape(shape: PieceShape) {
@ -89,7 +93,7 @@ async function createPuzzleTileBitmaps(img: ImageBitmap, tiles: Array<any>, info
const c2 = Graphics.createCanvas(tileDrawSize, tileDrawSize)
const ctx2 = c2.getContext('2d') as CanvasRenderingContext2D
for (let t of tiles) {
for (const t of tiles) {
const tile = Util.decodeTile(t)
const srcRect = srcRectByIdx(info, 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
}
function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number) {
function srcRectByIdx(puzzleInfo: PuzzleInfo, idx: number): Rect {
const c = Util.coordByTileIdx(puzzleInfo, idx)
return {
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
const bmp = await Graphics.loadImageToBitmap(puzzle.info.imageUrl)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import Util from './../common/Util'
import { Rng } from './../common/Rng'
import Images from './Images'
import { EncodedPiece, EncodedPieceShape, PieceShape } from '../common/GameCommon'
interface PuzzleInfo {
width: number
@ -31,7 +32,7 @@ async function createPuzzle(
if (!dim || !dim.width || !dim.height) {
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)
for (let i = 0; i < tiles.length; i++) {
@ -91,7 +92,7 @@ async function createPuzzle(
// then shuffle the positions
positions = rng.shuffle(positions)
tiles = tiles.map(tile => {
const pieces: Array<EncodedPiece> = tiles.map(tile => {
return Util.encodeTile({
idx: tile.idx, // index of tile in the array
group: 0, // if grouped with other tiles
@ -113,7 +114,7 @@ async function createPuzzle(
// Complete puzzle object
return {
// tiles array
tiles,
tiles: pieces,
// game data for puzzle, data changes during the game
data: {
// TODO: maybe calculate this each time?
@ -159,10 +160,10 @@ async function createPuzzle(
function determinePuzzleTileShapes(
rng: Rng,
info: PuzzleInfo
) {
): Array<EncodedPieceShape> {
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++) {
let coord = Util.coordByTileIdx(info, 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 tiles = tilesX * tilesY
const tileSize = TILE_SIZE

View file

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