From 8151663860f0e80470a94a2ad785e7eb0a9f126a Mon Sep 17 00:00:00 2001 From: Atul Varma Date: Wed, 12 May 2021 21:55:18 -0400 Subject: [PATCH] Add 'always include this symbol' dropdown. (#115) Fixes #36. --- lib/pages/creature-page.tsx | 76 +++++++++++++++++++++++++++++++++---- lib/randomizer-widget.tsx | 1 + lib/svg-symbol.tsx | 9 +++++ lib/svg-vocabulary.ts | 7 +++- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/lib/pages/creature-page.tsx b/lib/pages/creature-page.tsx index 6d417e8..53ed6ad 100644 --- a/lib/pages/creature-page.tsx +++ b/lib/pages/creature-page.tsx @@ -1,6 +1,10 @@ -import React, { useContext, useRef, useState } from "react"; -import { SvgVocabulary } from "../svg-vocabulary"; -import { noFillIfShowingSpecs, SvgSymbolData } from "../svg-symbol"; +import React, { useContext, useMemo, useRef, useState } from "react"; +import { SvgVocabulary, SvgVocabularyWithBlank } from "../svg-vocabulary"; +import { + EMPTY_SVG_SYMBOL_DATA, + noFillIfShowingSpecs, + SvgSymbolData, +} from "../svg-symbol"; import { AttachmentPointType, ATTACHMENT_POINT_TYPES, @@ -27,6 +31,7 @@ import { } from "../svg-composition-context"; import { Page } from "../page"; import { RandomizerWidget } from "../randomizer-widget"; +import { VocabularyWidget } from "../vocabulary-widget"; /** Symbols that can be the "root" (i.e., main body) of a creature. */ const ROOT_SYMBOLS = SvgVocabulary.items.filter( @@ -174,6 +179,40 @@ function getDownloadBasename(randomSeed: number) { return `mystic-symbolic-creature-${randomSeed}`; } +function creatureHasSymbol( + creature: CreatureSymbol, + symbol: SvgSymbolData +): boolean { + if (creature.data === symbol) return true; + if (creature.attachments.some((a) => creatureHasSymbol(a, symbol))) + return true; + return creature.nests.some((n) => creatureHasSymbol(n, symbol)); +} + +function repeatUntilSymbolIsIncluded( + symbol: SvgSymbolData, + rng: Random, + createCreature: (rng: Random) => CreatureSymbol, + maxAttempts = 10_000 +): CreatureSymbol { + if (symbol === EMPTY_SVG_SYMBOL_DATA) return createCreature(rng); + + for (let i = 0; i < maxAttempts; i++) { + const creature = createCreature(rng); + if (creatureHasSymbol(creature, symbol)) return creature; + } + + // We don't want to hold up the UI forever so just log a message and + // return *something*. + + console.log( + `Tried to create a creature with the ${symbol.name} symbol ` + + `but gave up after ${maxAttempts} attempts.` + ); + + return createCreature(rng); +} + export const CreaturePage: React.FC<{}> = () => { const svgRef = useRef(null); const [randomSeed, setRandomSeed] = useState(Date.now()); @@ -186,10 +225,22 @@ export const CreaturePage: React.FC<{}> = () => { ...defaultCtx, ...compCtx, }); - const creature = COMPLEXITY_LEVEL_GENERATORS[complexity]({ - rng: new Random(randomSeed), - randomlyInvert, - }); + const [alwaysInclude, setAlwaysInclude] = useState( + EMPTY_SVG_SYMBOL_DATA + ); + const creature = useMemo( + () => + repeatUntilSymbolIsIncluded( + alwaysInclude, + new Random(randomSeed), + (rng) => + COMPLEXITY_LEVEL_GENERATORS[complexity]({ + rng, + randomlyInvert, + }) + ), + [alwaysInclude, complexity, randomSeed, randomlyInvert] + ); return ( @@ -218,7 +269,16 @@ export const CreaturePage: React.FC<{}> = () => { setCompCtx({ ...compCtx, ...colors })} onSymbolsChange={newRandomSeed} - /> + > +
+ +
+
= (props) => { {randType !== "symbols" && ( )} + {props.children} diff --git a/lib/svg-symbol.tsx b/lib/svg-symbol.tsx index 4373abc..96769e4 100644 --- a/lib/svg-symbol.tsx +++ b/lib/svg-symbol.tsx @@ -16,6 +16,15 @@ export type SvgSymbolData = { specs?: Specs; }; +export const EMPTY_SVG_SYMBOL_DATA: SvgSymbolData = { + name: "", + bbox: { + x: { min: 0, max: 0 }, + y: { min: 0, max: 0 }, + }, + layers: [], +}; + export type SvgSymbolElement = ( | { tagName: "g"; diff --git a/lib/svg-vocabulary.ts b/lib/svg-vocabulary.ts index 0cf1e97..abca588 100644 --- a/lib/svg-vocabulary.ts +++ b/lib/svg-vocabulary.ts @@ -1,5 +1,10 @@ -import type { SvgSymbolData } from "./svg-symbol"; +import { EMPTY_SVG_SYMBOL_DATA, SvgSymbolData } from "./svg-symbol"; import { Vocabulary } from "./vocabulary"; import _SvgVocabulary from "./_svg-vocabulary"; export const SvgVocabulary = new Vocabulary(_SvgVocabulary); + +export const SvgVocabularyWithBlank = new Vocabulary([ + EMPTY_SVG_SYMBOL_DATA, + ..._SvgVocabulary, +]);