diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 8adee905..ee89f772 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -4,7 +4,7 @@ import { BasicSerializableObject, types } from "../savegame/serialization"; import { enumColors } from "./colors"; import { enumItemProcessorTypes } from "./components/item_processor"; import { GameRoot } from "./root"; -import { enumSubShape, ShapeDefinition } from "./shape_definition"; +import { ShapeDefinition, subShapes } from "./shape_definition"; import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals"; import { UPGRADES } from "./upgrades"; @@ -328,7 +328,7 @@ export class HubGoals extends BasicSerializableObject { let layers = []; const randomColor = () => randomChoice(Object.values(enumColors)); - const randomShape = () => randomChoice(Object.values(enumSubShape)); + const randomShape = () => randomChoice(Object.values(subShapes)); let anyIsMissingTwo = false; diff --git a/src/js/game/hud/parts/shape_viewer.js b/src/js/game/hud/parts/shape_viewer.js index 55feb976..4f856a57 100644 --- a/src/js/game/hud/parts/shape_viewer.js +++ b/src/js/game/hud/parts/shape_viewer.js @@ -2,7 +2,7 @@ import { InputReceiver } from "../../../core/input_receiver"; import { makeDiv, removeAllChildren } from "../../../core/utils"; import { T } from "../../../translations"; import { KeyActionMapper, KEYMAPPINGS } from "../../key_action_mapper"; -import { ShapeDefinition } from "../../shape_definition"; +import { ShapeDefinition, ShapeLayer } from "../../shape_definition"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; @@ -82,12 +82,13 @@ export class HUDShapeViewer extends BaseHUDPart { this.currentShapeKey = definition.getHash(); const layers = definition.layers; - this.contentDiv.setAttribute("data-layers", layers.length); + this.contentDiv.setAttribute("data-layers", `${layers.length}`); for (let i = 0; i < layers.length; ++i) { const layerElem = makeDiv(this.renderArea, null, ["layer", "layer-" + i]); - let fakeLayers = []; + /** @type {Array} **/ + const fakeLayers = []; for (let k = 0; k < i; ++k) { fakeLayers.push([null, null, null, null]); } diff --git a/src/js/game/map_chunk.js b/src/js/game/map_chunk.js index 54af1125..668deac0 100644 --- a/src/js/game/map_chunk.js +++ b/src/js/game/map_chunk.js @@ -8,7 +8,7 @@ import { enumColors } from "./colors"; import { Entity } from "./entity"; import { COLOR_ITEM_SINGLETONS } from "./items/color_item"; import { GameRoot } from "./root"; -import { enumSubShape } from "./shape_definition"; +import { subShapes } from "./shape_definition"; import { Rectangle } from "../core/rectangle"; const logger = createLogger("map_chunk"); @@ -179,31 +179,30 @@ export class MapChunk { * @param {number} distanceToOriginInChunks */ internalGenerateShapePatch(rng, shapePatchSize, distanceToOriginInChunks) { - /** @type {[enumSubShape, enumSubShape, enumSubShape, enumSubShape]} */ + /** @type {[SubShape, SubShape, SubShape, SubShape]} */ let subShapes = null; - let weights = {}; - // Later there is a mix of everything - weights = { - [enumSubShape.rect]: 100, - [enumSubShape.circle]: Math.round(50 + clamp(distanceToOriginInChunks * 2, 0, 50)), - [enumSubShape.star]: Math.round(20 + clamp(distanceToOriginInChunks, 0, 30)), - [enumSubShape.windmill]: Math.round(6 + clamp(distanceToOriginInChunks / 2, 0, 20)), + /** @type {Record} **/ + const weights = { + rect: 100, + circle: Math.round(50 + clamp(distanceToOriginInChunks * 2, 0, 50)), + star: Math.round(20 + clamp(distanceToOriginInChunks, 0, 30)), + windmill: Math.round(6 + clamp(distanceToOriginInChunks / 2, 0, 20)), }; if (distanceToOriginInChunks < 7) { // Initial chunks can not spawn the good stuff - weights[enumSubShape.star] = 0; - weights[enumSubShape.windmill] = 0; + weights.star = 0; + weights.windmill = 0; } if (distanceToOriginInChunks < 10) { - // Initial chunk patches always have the same shape + // Initial chunk patches always have the same subshape const subShape = this.internalGenerateRandomSubShape(rng, weights); subShapes = [subShape, subShape, subShape, subShape]; } else if (distanceToOriginInChunks < 15) { - // Later patches can also have mixed ones + // Later patches can have mixed halves const subShapeA = this.internalGenerateRandomSubShape(rng, weights); const subShapeB = this.internalGenerateRandomSubShape(rng, weights); subShapes = [subShapeA, subShapeA, subShapeB, subShapeB]; @@ -218,15 +217,13 @@ export class MapChunk { } // Makes sure windmills never spawn as whole - let windmillCount = 0; - for (let i = 0; i < subShapes.length; ++i) { - if (subShapes[i] === enumSubShape.windmill) { - ++windmillCount; - } - } + const windmillCount = subShapes.reduce((sum, subShape) => { + return sum + Number(subShape === "windmill"); + }, 0); + if (windmillCount > 1) { - subShapes[0] = enumSubShape.rect; - subShapes[1] = enumSubShape.rect; + subShapes[0] = "rect"; + subShapes[1] = "rect"; } const definition = this.root.shapeDefinitionMgr.getDefinitionFromSimpleShapes(subShapes); @@ -240,25 +237,26 @@ export class MapChunk { /** * Chooses a random shape with the given weights * @param {RandomNumberGenerator} rng - * @param {Object.} weights - * @returns {enumSubShape} + * @param {Record} weights + * @returns {SubShape} */ internalGenerateRandomSubShape(rng, weights) { - // @ts-ignore const sum = Object.values(weights).reduce((a, b) => a + b, 0); const chosenNumber = rng.nextIntRange(0, sum - 1); + let accumulated = 0; - for (const key in weights) { - const weight = weights[key]; + for (let i = 0; i < subShapes.length; i += 1) { + const shape = subShapes[i]; + const weight = weights[shape]; if (accumulated + weight > chosenNumber) { - return key; + return shape; } accumulated += weight; } logger.error("Failed to find matching shape in chunk generation"); - return enumSubShape.circle; + return "circle"; } /** diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js index 5b19af16..7ef3a5d3 100644 --- a/src/js/game/shape_definition.js +++ b/src/js/game/shape_definition.js @@ -9,16 +9,35 @@ import { THEME } from "./theme"; /** * @typedef {{ - * subShape: enumSubShape, + * subShape: SubShape, * color: enumColors, * }} ShapeLayerItem - */ - -/** + * * Order is Q1 (tr), Q2(br), Q3(bl), Q4(tl) * @typedef {[ShapeLayerItem?, ShapeLayerItem?, ShapeLayerItem?, ShapeLayerItem?]} ShapeLayer + * + * @typedef {"R" | "C" | "S" | "W"} SubShapeShortCode; */ +/** @type {SubShape[]} **/ +export const subShapes = ["rect", "circle", "star", "windmill"]; + +/** @type {Record} **/ +const subShapeShortCodeMap = { + rect: "R", + circle: "C", + star: "S", + windmill: "W", +}; + +/** @type {Record} **/ +const shortCodeSubShapeMap = { + R: "rect", + C: "circle", + S: "star", + W: "windmill", +}; + const arrayQuadrantIndexToOffset = [ new Vector(1, -1), // tr new Vector(1, 1), // br @@ -26,44 +45,6 @@ const arrayQuadrantIndexToOffset = [ new Vector(-1, -1), // tl ]; -/** @enum {string} */ -export const enumSubShape = { - rect: "rect", - circle: "circle", - star: "star", - windmill: "windmill", -}; - -/** @enum {string} */ -export const enumSubShapeToShortcode = { - [enumSubShape.rect]: "R", - [enumSubShape.circle]: "C", - [enumSubShape.star]: "S", - [enumSubShape.windmill]: "W", -}; - -/** @enum {enumSubShape} */ -export const enumShortcodeToSubShape = {}; -for (const key in enumSubShapeToShortcode) { - enumShortcodeToSubShape[enumSubShapeToShortcode[key]] = key; -} - -/** - * Converts the given parameters to a valid shape definition - * @param {*} layers - * @returns {Array} - */ -export function createSimpleShape(layers) { - layers.forEach(layer => { - layer.forEach(item => { - if (item) { - item.color = item.color || enumColors.uncolored; - } - }); - }); - return layers; -} - /** * Cache which shapes are valid short keys and which not * @type {Map} @@ -120,6 +101,7 @@ export class ShapeDefinition extends BasicSerializableObject { static fromShortKey(key) { const sourceLayers = key.split(":"); let layers = []; + for (let i = 0; i < sourceLayers.length; ++i) { const text = sourceLayers[i]; assert(text.length === 8, "Invalid shape short key: " + key); @@ -128,7 +110,7 @@ export class ShapeDefinition extends BasicSerializableObject { const quads = [null, null, null, null]; for (let quad = 0; quad < 4; ++quad) { const shapeText = text[quad * 2 + 0]; - const subShape = enumShortcodeToSubShape[shapeText]; + const subShape = shortCodeSubShapeMap[shapeText]; const color = enumShortcodeToColor[text[quad * 2 + 1]]; if (subShape) { assert(color, "Invalid shape short key:", key); @@ -173,6 +155,7 @@ export class ShapeDefinition extends BasicSerializableObject { static isValidShortKeyInternal(key) { const sourceLayers = key.split(":"); let layers = []; + for (let i = 0; i < sourceLayers.length; ++i) { const text = sourceLayers[i]; if (text.length !== 8) { @@ -185,7 +168,7 @@ export class ShapeDefinition extends BasicSerializableObject { for (let quad = 0; quad < 4; ++quad) { const shapeText = text[quad * 2 + 0]; const colorText = text[quad * 2 + 1]; - const subShape = enumShortcodeToSubShape[shapeText]; + const subShape = shortCodeSubShapeMap[shapeText]; const color = enumShortcodeToColor[colorText]; // Valid shape @@ -256,7 +239,7 @@ export class ShapeDefinition extends BasicSerializableObject { for (let quadrant = 0; quadrant < layer.length; ++quadrant) { const item = layer[quadrant]; if (item) { - id += enumSubShapeToShortcode[item.subShape] + enumColorToShortcode[item.color]; + id += subShapeShortCodeMap[item.subShape] + enumColorToShortcode[item.color]; } else { id += "--"; } @@ -359,7 +342,7 @@ export class ShapeDefinition extends BasicSerializableObject { const insetPadding = 0.0; switch (subShape) { - case enumSubShape.rect: { + case "rect": { context.beginPath(); const dims = quadrantSize * layerScale; context.rect( @@ -371,7 +354,7 @@ export class ShapeDefinition extends BasicSerializableObject { break; } - case enumSubShape.star: { + case "star": { context.beginPath(); const dims = quadrantSize * layerScale; @@ -387,7 +370,7 @@ export class ShapeDefinition extends BasicSerializableObject { break; } - case enumSubShape.windmill: { + case "windmill": { context.beginPath(); const dims = quadrantSize * layerScale; @@ -402,7 +385,7 @@ export class ShapeDefinition extends BasicSerializableObject { break; } - case enumSubShape.circle: { + case "circle": { context.beginPath(); context.moveTo(insetPadding + -quadrantHalfSize, -insetPadding + quadrantHalfSize); context.arc( diff --git a/src/js/game/shape_definition_manager.js b/src/js/game/shape_definition_manager.js index ef0d592f..644fda5e 100644 --- a/src/js/game/shape_definition_manager.js +++ b/src/js/game/shape_definition_manager.js @@ -3,9 +3,7 @@ import { BasicSerializableObject } from "../savegame/serialization"; import { enumColors } from "./colors"; import { ShapeItem } from "./items/shape_item"; import { GameRoot } from "./root"; -import { enumSubShape, ShapeDefinition } from "./shape_definition"; - -const logger = createLogger("shape_definition_manager"); +import { ShapeDefinition, ShapeLayer } from "./shape_definition"; export class ShapeDefinitionManager extends BasicSerializableObject { static getId() { @@ -246,14 +244,11 @@ export class ShapeDefinitionManager extends BasicSerializableObject { /** * - * @param {[enumSubShape, enumSubShape, enumSubShape, enumSubShape]} subShapes + * @param {[SubShape, SubShape, SubShape, SubShape]} subShapes * @returns {ShapeDefinition} */ getDefinitionFromSimpleShapes(subShapes, color = enumColors.uncolored) { - const shapeLayer = /** @type {import("./shape_definition").ShapeLayer} */ (subShapes.map( - subShape => ({ subShape, color }) - )); - + const shapeLayer = /** @type {ShapeLayer} */ (subShapes.map(subShape => ({ subShape, color }))); return this.registerOrReturnHandle(new ShapeDefinition({ layers: [shapeLayer] })); } } diff --git a/src/js/globals.d.ts b/src/js/globals.d.ts index 51e4a2c3..7241d00a 100644 --- a/src/js/globals.d.ts +++ b/src/js/globals.d.ts @@ -195,6 +195,7 @@ declare interface TypedSignal> { declare type Layer = "regular" | "wires"; declare type ItemType = "shape" | "color" | "boolean"; +declare type SubShape = "rect" | "circle" | "star" | "windmill"; declare module "worker-loader?inline=true&fallback=false!*" { class WebpackWorker extends Worker {