rodzic
f49fa7170d
commit
8151663860
|
@ -1,6 +1,10 @@
|
||||||
import React, { useContext, useRef, useState } from "react";
|
import React, { useContext, useMemo, useRef, useState } from "react";
|
||||||
import { SvgVocabulary } from "../svg-vocabulary";
|
import { SvgVocabulary, SvgVocabularyWithBlank } from "../svg-vocabulary";
|
||||||
import { noFillIfShowingSpecs, SvgSymbolData } from "../svg-symbol";
|
import {
|
||||||
|
EMPTY_SVG_SYMBOL_DATA,
|
||||||
|
noFillIfShowingSpecs,
|
||||||
|
SvgSymbolData,
|
||||||
|
} from "../svg-symbol";
|
||||||
import {
|
import {
|
||||||
AttachmentPointType,
|
AttachmentPointType,
|
||||||
ATTACHMENT_POINT_TYPES,
|
ATTACHMENT_POINT_TYPES,
|
||||||
|
@ -27,6 +31,7 @@ import {
|
||||||
} from "../svg-composition-context";
|
} from "../svg-composition-context";
|
||||||
import { Page } from "../page";
|
import { Page } from "../page";
|
||||||
import { RandomizerWidget } from "../randomizer-widget";
|
import { RandomizerWidget } from "../randomizer-widget";
|
||||||
|
import { VocabularyWidget } from "../vocabulary-widget";
|
||||||
|
|
||||||
/** Symbols that can be the "root" (i.e., main body) of a creature. */
|
/** Symbols that can be the "root" (i.e., main body) of a creature. */
|
||||||
const ROOT_SYMBOLS = SvgVocabulary.items.filter(
|
const ROOT_SYMBOLS = SvgVocabulary.items.filter(
|
||||||
|
@ -174,6 +179,40 @@ function getDownloadBasename(randomSeed: number) {
|
||||||
return `mystic-symbolic-creature-${randomSeed}`;
|
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<{}> = () => {
|
export const CreaturePage: React.FC<{}> = () => {
|
||||||
const svgRef = useRef<SVGSVGElement>(null);
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
const [randomSeed, setRandomSeed] = useState<number>(Date.now());
|
const [randomSeed, setRandomSeed] = useState<number>(Date.now());
|
||||||
|
@ -186,10 +225,22 @@ export const CreaturePage: React.FC<{}> = () => {
|
||||||
...defaultCtx,
|
...defaultCtx,
|
||||||
...compCtx,
|
...compCtx,
|
||||||
});
|
});
|
||||||
const creature = COMPLEXITY_LEVEL_GENERATORS[complexity]({
|
const [alwaysInclude, setAlwaysInclude] = useState<SvgSymbolData>(
|
||||||
rng: new Random(randomSeed),
|
EMPTY_SVG_SYMBOL_DATA
|
||||||
randomlyInvert,
|
);
|
||||||
});
|
const creature = useMemo(
|
||||||
|
() =>
|
||||||
|
repeatUntilSymbolIsIncluded(
|
||||||
|
alwaysInclude,
|
||||||
|
new Random(randomSeed),
|
||||||
|
(rng) =>
|
||||||
|
COMPLEXITY_LEVEL_GENERATORS[complexity]({
|
||||||
|
rng,
|
||||||
|
randomlyInvert,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
[alwaysInclude, complexity, randomSeed, randomlyInvert]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page title="Creature!">
|
<Page title="Creature!">
|
||||||
|
@ -218,7 +269,16 @@ export const CreaturePage: React.FC<{}> = () => {
|
||||||
<RandomizerWidget
|
<RandomizerWidget
|
||||||
onColorsChange={(colors) => setCompCtx({ ...compCtx, ...colors })}
|
onColorsChange={(colors) => setCompCtx({ ...compCtx, ...colors })}
|
||||||
onSymbolsChange={newRandomSeed}
|
onSymbolsChange={newRandomSeed}
|
||||||
/>
|
>
|
||||||
|
<div className="thingy">
|
||||||
|
<VocabularyWidget
|
||||||
|
label="Always include this symbol"
|
||||||
|
value={alwaysInclude}
|
||||||
|
onChange={setAlwaysInclude}
|
||||||
|
choices={SvgVocabularyWithBlank}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RandomizerWidget>
|
||||||
<div className="thingy">
|
<div className="thingy">
|
||||||
<ExportWidget
|
<ExportWidget
|
||||||
basename={getDownloadBasename(randomSeed)}
|
basename={getDownloadBasename(randomSeed)}
|
||||||
|
|
|
@ -65,6 +65,7 @@ export const RandomizerWidget: React.FC<RandomizerWidgetProps> = (props) => {
|
||||||
{randType !== "symbols" && (
|
{randType !== "symbols" && (
|
||||||
<PaletteAlgorithmWidget value={paletteAlg} onChange={setPaletteAlg} />
|
<PaletteAlgorithmWidget value={paletteAlg} onChange={setPaletteAlg} />
|
||||||
)}
|
)}
|
||||||
|
{props.children}
|
||||||
<button accessKey="r" onClick={randomize}>
|
<button accessKey="r" onClick={randomize}>
|
||||||
<u>R</u>andomize!
|
<u>R</u>andomize!
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -16,6 +16,15 @@ export type SvgSymbolData = {
|
||||||
specs?: Specs;
|
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 = (
|
export type SvgSymbolElement = (
|
||||||
| {
|
| {
|
||||||
tagName: "g";
|
tagName: "g";
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import type { SvgSymbolData } from "./svg-symbol";
|
import { EMPTY_SVG_SYMBOL_DATA, SvgSymbolData } from "./svg-symbol";
|
||||||
import { Vocabulary } from "./vocabulary";
|
import { Vocabulary } from "./vocabulary";
|
||||||
import _SvgVocabulary from "./_svg-vocabulary";
|
import _SvgVocabulary from "./_svg-vocabulary";
|
||||||
|
|
||||||
export const SvgVocabulary = new Vocabulary<SvgSymbolData>(_SvgVocabulary);
|
export const SvgVocabulary = new Vocabulary<SvgSymbolData>(_SvgVocabulary);
|
||||||
|
|
||||||
|
export const SvgVocabularyWithBlank = new Vocabulary<SvgSymbolData>([
|
||||||
|
EMPTY_SVG_SYMBOL_DATA,
|
||||||
|
..._SvgVocabulary,
|
||||||
|
]);
|
||||||
|
|
Ładowanie…
Reference in New Issue