Add attach_to metadata. (#52)

This fixes #49 by implementing the `attach_to` metadata property.
pull/54/head
Atul Varma 2021-03-18 19:32:05 -04:00 zatwierdzone przez GitHub
rodzic d67848fb67
commit 612195e2f2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
7 zmienionych plików z 130 dodań i 17 usunięć

Wyświetl plik

@ -1,7 +1,11 @@
import React, { useContext, useRef, useState } from "react";
import { SvgVocabulary } from "../svg-vocabulary";
import { createSvgSymbolContext, SvgSymbolData } from "../svg-symbol";
import { iterAttachmentPoints } from "../specs";
import {
AttachmentPointType,
ATTACHMENT_POINT_TYPES,
iterAttachmentPoints,
} from "../specs";
import { Random } from "../random";
import { SymbolContextWidget } from "../symbol-context-widget";
import { range } from "../util";
@ -34,8 +38,41 @@ const ROOT_SYMBOLS = SvgVocabulary.filter(
(data) => data.meta?.always_be_nested !== true
);
/** Symbols that can be attached to the main body of a creature. */
const ATTACHMENT_SYMBOLS = ROOT_SYMBOLS;
type AttachmentSymbolMap = {
[key in AttachmentPointType]: SvgSymbolData[];
};
/**
* Symbols that can be attached to the main body of a creature,
* at a particular attachment point.
*/
const ATTACHMENT_SYMBOLS: AttachmentSymbolMap = (() => {
const result = {} as AttachmentSymbolMap;
for (let type of ATTACHMENT_POINT_TYPES) {
result[type] = SvgVocabulary.filter((data) => {
const { meta } = data;
// If we have no metadata whatsoever, it can attach anywhere.
if (!meta) return true;
if (meta.always_be_nested === true) {
// This symbol should *only* ever be nested, so return false.
return false;
}
// If we have no "attach_to", it can attach anywhere.
if (!meta.attach_to) {
return true;
}
// Only attach to points listed in "attach_to".
return meta.attach_to.includes(type);
});
}
return result;
})();
/** Symbols that can be nested within any part of a creature. */
const NESTED_SYMBOLS = SvgVocabulary.filter(
@ -113,7 +150,7 @@ function getSymbolWithAttachments(
numAttachmentKinds
);
for (let kind of attachmentKinds) {
const attachment = rng.choice(ATTACHMENT_SYMBOLS);
const attachment = rng.choice(ATTACHMENT_SYMBOLS[kind]);
const indices = range(root.specs[kind]?.length ?? 0);
result.attachments.push({
data: attachment,

Wyświetl plik

@ -43,6 +43,14 @@ export const ATTACHMENT_POINT_TYPES: AttachmentPointType[] = [
"crown",
];
const ATTACHMENT_POINT_SET = new Set(ATTACHMENT_POINT_TYPES);
export function isAttachmentPointType(
value: any
): value is AttachmentPointType {
return ATTACHMENT_POINT_SET.has(value);
}
export function* iterAttachmentPoints(specs: Specs): Iterable<AttachmentPoint> {
for (let type of ATTACHMENT_POINT_TYPES) {
const points = specs[type];

Wyświetl plik

@ -1,7 +1,11 @@
import path from "path";
import fs from "fs";
import toml from "toml";
import { validateSvgSymbolMetadata } from "./svg-symbol-metadata";
import {
validateAttachTo,
validateSvgSymbolMetadata,
} from "./svg-symbol-metadata";
import { withMockConsoleLog } from "./test-util";
const templatePath = path.join(__dirname, "..", "svg", "_template.toml");
@ -51,3 +55,24 @@ describe("validateSvgSymbolMetadata()", () => {
});
});
});
describe("validateAttachTo()", () => {
it("works", () => {
expect(validateAttachTo(["tail", "leg"])).toEqual(["tail", "leg"]);
});
it("works", () => {
withMockConsoleLog((mockLog) => {
expect(validateAttachTo(["beanbag"])).toEqual([]);
expect(mockLog).toHaveBeenCalledWith(
"Item 'beanbag' in \"attach_to\" is not a valid attachment point."
);
});
});
it("raises error when value is not an array", () => {
expect(() => validateAttachTo("blah")).toThrow(
'Expected "attach_to" to be an array, but it is a string!'
);
});
});

Wyświetl plik

@ -1,3 +1,5 @@
import { AttachmentPointType, isAttachmentPointType } from "./specs";
type SvgSymbolMetadataBooleans = {
/**
* If true, this indicates that the symbol should always have
@ -37,7 +39,14 @@ function isSvgSymbolMetadataBoolean(
return METADATA_BOOLEANS.has(key as any);
}
export type SvgSymbolMetadata = SvgSymbolMetadataBooleans;
export type SvgSymbolMetadata = SvgSymbolMetadataBooleans & {
/**
* If defined, this indicates the kinds of attachment points
* that this symbol can attach to. If not defined, it will
* be able to attach to any symbol.
*/
attach_to?: AttachmentPointType[];
};
export function validateSvgSymbolMetadata(
obj: any
@ -53,9 +62,33 @@ export function validateSvgSymbolMetadata(
);
}
metadata[key] = value;
} else if (key === "attach_to") {
metadata.attach_to = validateAttachTo(obj[key]);
} else {
unknownProperties.push(key);
}
}
return { metadata, unknownProperties };
}
export function validateAttachTo(value: unknown): AttachmentPointType[] {
if (!Array.isArray(value)) {
throw new Error(
`Expected "attach_to" to be an array, but it is a ${typeof value}!`
);
}
const result: AttachmentPointType[] = [];
for (let item of value) {
if (isAttachmentPointType(item)) {
result.push(item);
} else {
console.log(
`Item '${item}' in "attach_to" is not a valid attachment point.`
);
}
}
return result;
}

10
lib/test-util.ts 100644
Wyświetl plik

@ -0,0 +1,10 @@
export function withMockConsoleLog(fn: (mock: jest.Mock) => void) {
const originalLog = console.log;
const mockLog = jest.fn();
console.log = mockLog;
try {
fn(mockLog);
} finally {
console.log = originalLog;
}
}

Wyświetl plik

@ -1,3 +1,4 @@
import { withMockConsoleLog } from "./test-util";
import { convertSvgMarkupToSymbolData } from "./vocabulary-builder";
const CIRCLE = `<path fill="#ffffff" fill-rule="evenodd" stroke="#000000" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" d="M 360.000 29.751 C 542.791 29.751 690.249 177.209 690.249 360.000 C 690.249 542.791 542.791 690.249 360.000 690.249 C 177.209 690.249 29.751 542.791 29.751 360.000 C 29.751 177.209 177.209 29.751 360.000 29.751 Z"/>`;
@ -6,17 +7,6 @@ function arrow(color: string) {
return `<path fill="${color}" fill-rule="evenodd" stroke="none" d="M 360.000 679.153 C 360.001 679.156 372.114 713.074 372.116 713.077 C 372.114 713.076 360.001 701.805 360.000 701.804 C 359.999 701.805 347.886 713.076 347.884 713.077 C 347.886 713.074 359.999 679.156 360.000 679.153 Z"/>`;
}
function withMockConsoleLog(fn: (mock: jest.Mock) => void) {
const originalLog = console.log;
const mockLog = jest.fn();
console.log = mockLog;
try {
fn(mockLog);
} finally {
console.log = originalLog;
}
}
describe("convertSvgMarkupToSymbolData()", () => {
it("works with SVGs that just have a path and no specs", () => {
expect(

Wyświetl plik

@ -7,6 +7,10 @@
#
# Also note that any lines that begin with a "#" are comments,
# so they will be ignored by the code.
#
# Note also that all properties that are booleans
# (i.e., true or false) will, unless otherwise noted,
# always default to false if left undefined.
# If true, this indicates that the symbol should always have
# a symbol nested within its nesting area(s).
@ -23,3 +27,9 @@ never_be_nested = false
# If true, this indicates that any symbols nested on this
# symbols nesting area should have their colors inverted.
invert_nested = false
# If set to an array of strings, this indicates the kinds of
# attachment points that this symbol can attach to. If not
# defined, this symbol will be able to attach to any attachment
# point.
attach_to = ["tail", "leg", "arm", "horn", "crown"]