From c13303185b1c0423d82328ea34138dec9d337e7e Mon Sep 17 00:00:00 2001 From: Bjorn Stromberg Date: Tue, 4 Aug 2020 19:59:35 +0900 Subject: [PATCH] Replace enumSubShape with SubShape --- src/js/game/hub_goals.js | 4 +- src/js/game/hud/parts/shape_viewer.js | 7 +- src/js/game/map_chunk.js | 106 +++++++++---------- src/js/game/shape_definition.js | 134 +++++++++++------------- src/js/game/shape_definition_manager.js | 14 +-- 5 files changed, 125 insertions(+), 140 deletions(-) diff --git a/src/js/game/hub_goals.js b/src/js/game/hub_goals.js index 36f8f107..e34f271d 100644 --- a/src/js/game/hub_goals.js +++ b/src/js/game/hub_goals.js @@ -5,7 +5,7 @@ import { BasicSerializableObject, types } from "../savegame/serialization"; import { enumColors } from "./colors"; import { enumItemProcessorTypes } from "./components/item_processor"; import { GameRoot, enumLayer } from "./root"; -import { enumSubShape, ShapeDefinition } from "./shape_definition"; +import { subShapes, ShapeDefinition } from "./shape_definition"; import { enumHubGoalRewards, tutorialGoals } from "./tutorial_goals"; import { UPGRADES, blueprintShape } from "./upgrades"; @@ -331,7 +331,7 @@ export class HubGoals extends BasicSerializableObject { // @ts-ignore const randomColor = () => randomChoice(Object.values(enumColors)); // @ts-ignore - 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..b6989d44 100644 --- a/src/js/game/hud/parts/shape_viewer.js +++ b/src/js/game/hud/parts/shape_viewer.js @@ -6,6 +6,8 @@ import { ShapeDefinition } from "../../shape_definition"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; +/** @typedef {import("../../shape_definition").ShapeLayer} ShapeLayer **/ + const copy = require("clipboard-copy"); export class HUDShapeViewer extends BaseHUDPart { @@ -82,12 +84,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 dd9ba81d..b8783f26 100644 --- a/src/js/game/map_chunk.js +++ b/src/js/game/map_chunk.js @@ -8,11 +8,38 @@ import { enumColors } from "./colors"; import { Entity } from "./entity"; import { ColorItem } from "./items/color_item"; import { ShapeItem } from "./items/shape_item"; -import { enumSubShape } from "./shape_definition"; +import { subShapes } from "./shape_definition"; import { RandomNumberGenerator } from "../core/rng"; +/** @typedef {import("./shape_definition").SubShape} SubShape **/ + const logger = createLogger("map_chunk"); +/** + * Chooses a random shape with the given weights + * @param {RandomNumberGenerator} rng + * @param {Record} weights + * @returns {SubShape} + */ +function generateRandomSubShape(rng, weights) { + const sum = Object.values(weights).reduce((a, b) => a + b, 0); + + const chosenNumber = rng.nextIntRange(0, sum - 1); + + let accumulated = 0; + for (let i = 0; i < subShapes.length; i += 1) { + const shape = subShapes[i]; + const weight = weights[shape]; + if (accumulated + weight > chosenNumber) { + return shape; + } + accumulated += weight; + } + + logger.error("Failed to find matching shape in chunk generation"); + return "circle"; +} + export class MapChunk { /** * @@ -117,8 +144,6 @@ export class MapChunk { avgPos.y += y; } } - } else { - // logger.warn("Tried to spawn resource out of chunk"); } } } @@ -153,84 +178,57 @@ 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 - const subShape = this.internalGenerateRandomSubShape(rng, weights); + // Initial chunk patches always have the same subshape + const subShape = generateRandomSubShape(rng, weights); subShapes = [subShape, subShape, subShape, subShape]; } else if (distanceToOriginInChunks < 15) { - // Later patches can also have mixed ones - const subShapeA = this.internalGenerateRandomSubShape(rng, weights); - const subShapeB = this.internalGenerateRandomSubShape(rng, weights); + // Later patches can have mixed halves + const subShapeA = generateRandomSubShape(rng, weights); + const subShapeB = generateRandomSubShape(rng, weights); subShapes = [subShapeA, subShapeA, subShapeB, subShapeB]; } else { // Finally there is a mix of everything subShapes = [ - this.internalGenerateRandomSubShape(rng, weights), - this.internalGenerateRandomSubShape(rng, weights), - this.internalGenerateRandomSubShape(rng, weights), - this.internalGenerateRandomSubShape(rng, weights), + generateRandomSubShape(rng, weights), + generateRandomSubShape(rng, weights), + generateRandomSubShape(rng, weights), + generateRandomSubShape(rng, weights), ]; } // 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); this.internalGeneratePatch(rng, shapePatchSize, new ShapeItem(definition)); } - /** - * Chooses a random shape with the given weights - * @param {RandomNumberGenerator} rng - * @param {Object.} weights - * @returns {enumSubShape} - */ - 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]; - if (accumulated + weight > chosenNumber) { - return key; - } - accumulated += weight; - } - - logger.error("Failed to find matching shape in chunk generation"); - return enumSubShape.circle; - } - /** * Generates the lower layer "terrain" */ diff --git a/src/js/game/shape_definition.js b/src/js/game/shape_definition.js index f6117a52..f4361870 100644 --- a/src/js/game/shape_definition.js +++ b/src/js/game/shape_definition.js @@ -19,17 +19,42 @@ const rusha = require("rusha"); const logger = createLogger("shape_definition"); /** + * @typedef {"rect" | "circle" | "star" | "windmill"} SubShape + * @typedef {"R" | "C" | "S" | "W"} SubShapeShortCode + * + * + * * @typedef {{ - * subShape: enumSubShape, + * subShape: SubShape, * color: enumColors, * }} ShapeLayerItem - */ - -/** + * * Order is Q1 (tr), Q2(br), Q3(bl), Q4(tl) * @typedef {[ShapeLayerItem?, ShapeLayerItem?, ShapeLayerItem?, ShapeLayerItem?]} ShapeLayer */ +/** @type {SubShape[]} **/ +export const subShapes = ["rect", "circle", "star", "windmill"]; + +/** @type {SubShapeShortCode[]} **/ +export const subShapeShortCodes = ["R", "C", "S", "W"]; + +/** @type {Record} **/ +export const subShapeShortCodeMap = { + rect: "R", + circle: "C", + star: "S", + windmill: "W", +}; + +/** @type {Record} **/ +export const shortCodeSubShapeMap = { + R: "rect", + C: "circle", + S: "star", + W: "windmill", +}; + const arrayQuadrantIndexToOffset = [ new Vector(1, -1), // tr new Vector(1, 1), // br @@ -37,44 +62,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} @@ -82,29 +69,7 @@ export function createSimpleShape(layers) { const SHORT_KEY_CACHE = new Map(); export class ShapeDefinition extends BasicSerializableObject { - static getId() { - return "ShapeDefinition"; - } - - static getSchema() { - return {}; - } - - deserialize(data) { - const errorCode = super.deserialize(data); - if (errorCode) { - return errorCode; - } - const definition = ShapeDefinition.fromShortKey(data); - this.layers = definition.layers; - } - - serialize() { - return this.getHash(); - } - /** - * * @param {object} param0 * @param {Array=} param0.layers */ @@ -123,6 +88,14 @@ export class ShapeDefinition extends BasicSerializableObject { this.bufferGenerator = null; } + static getId() { + return "ShapeDefinition"; + } + + static getSchema() { + return {}; + } + /** * Generates the definition from the given short key * @param {string} key @@ -130,7 +103,8 @@ export class ShapeDefinition extends BasicSerializableObject { */ static fromShortKey(key) { const sourceLayers = key.split(":"); - let layers = []; + const layers = []; + for (let i = 0; i < sourceLayers.length; ++i) { const text = sourceLayers[i]; assert(text.length === 8, "Invalid shape short key: " + key); @@ -139,7 +113,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); @@ -183,7 +157,8 @@ export class ShapeDefinition extends BasicSerializableObject { */ static isValidShortKeyInternal(key) { const sourceLayers = key.split(":"); - let layers = []; + const layers = []; + for (let i = 0; i < sourceLayers.length; ++i) { const text = sourceLayers[i]; if (text.length !== 8) { @@ -196,7 +171,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 @@ -235,6 +210,19 @@ export class ShapeDefinition extends BasicSerializableObject { return true; } + deserialize(data) { + const errorCode = super.deserialize(data); + if (errorCode) { + return errorCode; + } + const definition = ShapeDefinition.fromShortKey(data); + this.layers = definition.layers; + } + + serialize() { + return this.getHash(); + } + /** * Internal method to clone the shape definition * @returns {Array} @@ -267,7 +255,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 += "--"; } @@ -369,7 +357,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( @@ -381,7 +369,7 @@ export class ShapeDefinition extends BasicSerializableObject { break; } - case enumSubShape.star: { + case "star": { context.beginPath(); const dims = quadrantSize * layerScale; @@ -397,7 +385,7 @@ export class ShapeDefinition extends BasicSerializableObject { break; } - case enumSubShape.windmill: { + case "windmill": { context.beginPath(); const dims = quadrantSize * layerScale; @@ -412,7 +400,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 a3b8c841..9b567926 100644 --- a/src/js/game/shape_definition_manager.js +++ b/src/js/game/shape_definition_manager.js @@ -1,10 +1,10 @@ import { BasicSerializableObject } from "../savegame/serialization"; import { GameRoot } from "./root"; -import { ShapeDefinition, enumSubShape } from "./shape_definition"; -import { createLogger } from "../core/logging"; +import { ShapeDefinition } from "./shape_definition"; import { enumColors } from "./colors"; -const logger = createLogger("shape_definition_manager"); +/** @typedef {import("./shape_definition").SubShape} SubShape **/ +/** @typedef {import("./shape_definition").ShapeLayer} ShapeLayer **/ export class ShapeDefinitionManager extends BasicSerializableObject { static getId() { @@ -223,20 +223,16 @@ export class ShapeDefinitionManager extends BasicSerializableObject { return this.shapeKeyToDefinition[id]; } this.shapeKeyToDefinition[id] = definition; - // logger.log("Registered shape with key (2)", id); return definition; } /** * - * @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] })); } }