Compare commits

...

2 Commits

3 changed files with 140 additions and 42 deletions

View File

@ -6,7 +6,7 @@ import { nameLibFigureParameters, Move, chooser_pairz } from "./libfigureMapper.
import { LowLevelMove, SemanticAnimation, SemanticAnimationKind, animateFromLowLevelMoves } from "./lowLevelMove.js";
import { BalanceWeight, CirclePosition, CircleSide, CircleSideOrCenter, DancerDistance, Facing, HandConnection, HandTo, LongLines, PositionKind, SemanticPosition, ShortLinesPosition, StarGrip, handToDancerToSideInCircleFacingAcross, handsFourImproper, handsInCircle, handsInLine, handsInShortLine, oppositeFacing } from "./interpreterCommon.js";
import { ContraDBDance } from "./danceLibrary.js";
import { errorMoveInterpreterCtor, moveInterpreters } from "./moves/_moveInterpreter.js";
import { AllVariantsForMoveArgs, ErrorMoveInterpreter, LowLevelMovesForAllDancers, MoveAsLowLevelMovesArgs, SemanticPositionsForAllDancers, Variant, VariantCollection, errorMoveInterpreterCtor, moveInterpreters } from "./moves/_moveInterpreter.js";
// Import all individual move files.
// ls [a-z]*.js | sed 's@.*@import "./moves/\0";@'
@ -51,51 +51,90 @@ function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: {
return new moveInterpreter({ move, nextMove, numProgessions }).moveAsLowLevelMoves({ startingPos });
}
function danceAsLowLevelMoves(moves: Move[], startingPos: Map<DancerIdentity, SemanticPosition>): Map<DancerIdentity, LowLevelMove[]> {
const res = new Map<DancerIdentity, LowLevelMove[]>([...startingPos.keys()].map(id => [id, []]));
let currentPos = new Map<DancerIdentity, SemanticPosition>(startingPos);
// TODO Get rid of nextMove here once variants are actually supported by Swing.
function allVariantsForMove({ move, nextMove, startingVariants, numProgessions }: { move: Move; nextMove: Move; startingVariants: AllVariantsForMoveArgs; numProgessions: number; }): VariantCollection {
const moveInterpreter = moveInterpreters.get(move.move) ?? errorMoveInterpreterCtor;
return new moveInterpreter({ move, nextMove, numProgessions }).allVariantsForMove(startingVariants);
}
function danceAsLowLevelMoves(moves: Move[], startingVariants: AllVariantsForMoveArgs): Map<DancerIdentity, LowLevelMove[]> {
let currentVariants = new Map<string, MoveAsLowLevelMovesArgs>(startingVariants);
let currentVariantMoves: Map<string, LowLevelMovesForAllDancers> = new Map<string, LowLevelMovesForAllDancers>(
[...startingVariants.keys()].map(name => [name, new Map<DancerIdentity, LowLevelMove[]>(DancerIdentity.all().map(id => [id, []]))]));
let numProgessions = 0;
for (let i = 0; i < moves.length; i++) {
const move = moves[i];
const nextMove = i === moves.length - 1 ? moves[0] : moves[i + 1];
function updateVariants(newVariants: VariantCollection) {
let previousVariants = currentVariants;
let previousVariantMoves = currentVariantMoves;
try {
currentVariants = new Map<string, MoveAsLowLevelMovesArgs>();
currentVariantMoves = new Map<string, LowLevelMovesForAllDancers>();
for (const [name, variant] of newVariants) {
const oldMoves = previousVariantMoves.get(variant.previousMoveVariant);
if (!oldMoves) {
throw new Error("Referenced unknown previous variant: " + variant.previousMoveVariant);
}
const newMoves: LowLevelMovesForAllDancers = new Map<DancerIdentity, LowLevelMove[]>();
const newPositions = new Map<DancerIdentity, SemanticPosition>();
for (const [id, newMoveList] of variant.lowLevelMoves.entries()) {
newMoves.set(id, [...oldMoves.get(id)!, ...newMoveList]);
newPositions.set(id, newMoveList.at(-1)!.endPosition);
}
currentVariants.set(name, { startingPos: newPositions });
currentVariantMoves.set(name, newMoves);
}
} catch (ex) {
currentVariants = previousVariants;
currentVariantMoves = previousVariantMoves;
throw ex;
}
}
try {
if (i > 0 && move.beats === 0 && move.move === "slide along set") {
const slideLeft: boolean = move.parameters.slide;
for (const [id, currPos] of currentPos.entries()) {
const slideAmount = (currPos.which.leftRightSide() === CircleSide.Left) === slideLeft ? +0.5 : -0.5;
const setOffset = (currPos.setOffset ?? 0) + slideAmount;
currentPos.set(id, { ...currPos, setOffset });
for (const [name, currentPos] of currentVariants.entries()) {
for (const [id, currPos] of currentPos.startingPos.entries()) {
const slideAmount = (currPos.which.leftRightSide() === CircleSide.Left) === slideLeft ? +0.5 : -0.5;
const setOffset = (currPos.setOffset ?? 0) + slideAmount;
currentPos.startingPos.set(id, { ...currPos, setOffset });
const prevMove = res.get(id)!.at(-1)!;
prevMove.movementPattern.setSlideAmount = slideAmount;
prevMove.endPosition.setOffset = setOffset;
const prevMove = currentVariantMoves.get(name)!.get(id)!.at(-1)!;
prevMove.movementPattern.setSlideAmount = slideAmount;
prevMove.endPosition.setOffset = setOffset;
}
}
} else {
const newMoves = moveAsLowLevelMoves({ move, nextMove, startingPos: currentPos, numProgessions });
for (const [id, newMoveList] of newMoves.entries()) {
res.get(id)!.push(...newMoveList);
currentPos.set(id, newMoveList.at(-1)!.endPosition);
const newMoves = allVariantsForMove({ move, nextMove, startingVariants: currentVariants, numProgessions });
if (newMoves.size === 0) {
updateVariants(new ErrorMoveInterpreter({ move, nextMove, numProgessions },
"ERROR: " + move.move + " has no valid starting variants.").allVariantsForMove(currentVariants));
} else {
updateVariants(newMoves);
}
}
}
catch (ex) {
// catch exception so something can be displayed
for (const [id, pos] of currentPos.entries()) {
res.get(id)!.push({
beats: move.beats,
startPosition: pos,
endPosition: pos,
movementPattern: { kind: SemanticAnimationKind.StandStill },
move,
startBeat: 0,
interpreterError: ex instanceof Error ? ex.message : ex,
});
}
const errorMessage: string = ex instanceof Error ? ex.message : ex;
const errorVariants = new ErrorMoveInterpreter({ move, nextMove, numProgessions }, errorMessage).allVariantsForMove(currentVariants);
updateVariants(errorVariants);
}
if (move.progression) numProgessions++;
}
const res = [...currentVariantMoves.values()][0];
if (currentVariantMoves.size !== 1) {
res.get(DancerIdentity.OnesRobin)![0].interpreterError = "Expected exactly one variant. Found " + currentVariantMoves.size;
}
try {
const progression = animateFromLowLevelMoves(res).progression;
const progressionInSets = progression.y / setDistance;
@ -198,6 +237,15 @@ function StartingPosForFormation(formation: common.StartFormation, dance?: Contr
}
}
function startingVariantsForFormation(formation: common.StartFormation, dance?: ContraDBDance) : AllVariantsForMoveArgs {
// TODO Handle various different starting position that all count as "improper" like short/long waves, etc.
const pos = StartingPosForFormation(formation, dance);
return new Map<string, MoveAsLowLevelMovesArgs>([
["Initial", { startingPos: pos }]
]);
}
export let mappedDance: Move[];
export let interpretedDance: Map<DancerIdentity, LowLevelMove[]>;
@ -205,7 +253,7 @@ export let interpretedAnimation: animation.Animation;
export function loadDance(dance: ContraDBDance): animation.Animation {
mappedDance = dance.figures.map(nameLibFigureParameters);
interpretedDance = danceAsLowLevelMoves(mappedDance, StartingPosForFormation(dance.start_type, dance));
interpretedDance = danceAsLowLevelMoves(mappedDance, startingVariantsForFormation(dance.start_type, dance));
interpretedAnimation = animateFromLowLevelMoves(interpretedDance);
return interpretedAnimation;

View File

@ -9,7 +9,7 @@ export const moveInterpreters: Map<MoveName, MoveInterpreterCtor<MoveName>> = ne
export interface MoveInterpreterCtorArgs<N extends MoveName> {
move: Move & { move: N };
nextMove: Move;
nextMove?: Move;
numProgessions: number;
}
export type SemanticPositionsForAllDancers = Map<DancerIdentity, SemanticPosition>;
@ -18,11 +18,13 @@ export interface MoveAsLowLevelMovesArgs {
}
export type LowLevelMovesForAllDancers = Map<DancerIdentity, LowLevelMove[]>;
export interface Variant {
previousMoveVariant?: string,
previousMoveVariant: string,
lowLevelMoves: LowLevelMovesForAllDancers,
};
export type VariantCollection = Map<string, Variant>;
export type AllVariantsForMoveArgs = Map<string, MoveAsLowLevelMovesArgs>;
export type PartialLowLevelMove = {
remarks?: string,
beats: number,
@ -33,7 +35,7 @@ export type PartialLowLevelMove = {
export interface ISingleVariantMoveInterpreter {
moveAsLowLevelMoves: () => LowLevelMovesForAllDancers;
moveAsVariants: () => VariantCollection;
moveAsVariants: (previousMoveVariant: string | undefined) => VariantCollection;
};
export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>, N extends MoveName> implements ISingleVariantMoveInterpreter {
@ -51,9 +53,11 @@ export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>,
abstract moveAsLowLevelMoves(): LowLevelMovesForAllDancers;
moveAsVariants(): VariantCollection {
// TODO It would make more sense for previousMoveVariant to get passed into the constructor...
// ... but that requires touching every subclass, so it's an annoying refactor.
moveAsVariants(previousMoveVariant: string): VariantCollection {
return new Map<string, Variant>([
["default", { lowLevelMoves: this.moveAsLowLevelMoves() }]
["default", { lowLevelMoves: this.moveAsLowLevelMoves(), previousMoveVariant }]
]);
}
@ -293,10 +297,10 @@ export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>,
} : undefined);
}
errorStandStill() {
errorStandStill(error?: string) {
return this.handleMove(({ startPos }) => {
return [{
interpreterError: "UNKNOWN MOVE '" + this.move.move + "': standing still",
interpreterError: error ?? ("UNKNOWN MOVE '" + this.move.move + "': standing still"),
move: this.move,
startBeat: 0,
beats: this.move.beats,
@ -312,7 +316,7 @@ export abstract class SingleVariantMoveInterpreter<T extends MoveInterpreter<N>,
export abstract class MoveInterpreter<N extends MoveName> {
public readonly move: Move & { move: N };
public readonly nextMove: Move;
public readonly nextMove?: Move;
public readonly numProgressions: number;
constructor({ move, nextMove, numProgessions }: MoveInterpreterCtorArgs<N>) {
@ -326,16 +330,62 @@ export abstract class MoveInterpreter<N extends MoveName> {
moveAsLowLevelMoves({ startingPos }: MoveAsLowLevelMovesArgs): LowLevelMovesForAllDancers {
return this.buildSingleVariantMoveInterpreter(startingPos).moveAsLowLevelMoves();
}
// TODO Better name?
moveAsVariants({ startingPos }: MoveAsLowLevelMovesArgs, previousMoveVariant?: string): VariantCollection {
return this.buildSingleVariantMoveInterpreter(startingPos).moveAsVariants(previousMoveVariant);
}
allVariantsForMove(args: AllVariantsForMoveArgs): VariantCollection {
const res = new Map<string, Variant>();
let error;
for (const [variantName, variantArgs] of args.entries()) {
let newVariants: VariantCollection;
try {
newVariants = this.moveAsVariants(variantArgs, variantName);
} catch (ex) {
// TODO Maybe have a way to distinguish invalid start from error processing move?
error = ex;
// If this variant can't be processed, just continue.
continue;
}
for (const [newVariantName, variant] of newVariants) {
let combinedVariantName: string;
if (args.size === 1) {
combinedVariantName = newVariantName;
} else if (newVariants.size === 1) {
combinedVariantName = variantName;
} else {
combinedVariantName = newVariantName + "_from_" + variantName;
}
res.set(combinedVariantName, variant);
}
}
if (res.size === 0) throw error;
return res;
}
}
class DefaultSingleVariantMoveInterpreter extends SingleVariantMoveInterpreter<DefaultMoveInterpreter, MoveName> {
class ErrorSingleVariantMoveInterpreter extends SingleVariantMoveInterpreter<ErrorMoveInterpreter, MoveName> {
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
return this.errorStandStill();
return this.errorStandStill(this.moveInterpreter.error);
}
}
class DefaultMoveInterpreter extends MoveInterpreter<MoveName> {
export class ErrorMoveInterpreter extends MoveInterpreter<MoveName> {
public readonly error?: string;
constructor(args: MoveInterpreterCtorArgs<MoveName>, error?: string) {
super(args);
this.error = error;
}
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
throw new Error("Method not implemented.");
//throw new Error("Method not implemented.");
return new ErrorSingleVariantMoveInterpreter(this, startingPos);
}
}
export const errorMoveInterpreterCtor: MoveInterpreterCtor<MoveName> = DefaultMoveInterpreter;
export const errorMoveInterpreterCtor: MoveInterpreterCtor<MoveName> = ErrorMoveInterpreter;Error

View File

@ -10,7 +10,7 @@ const moveName: Move["move"] = "swing";
class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof moveName> {
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
// TODO Use variants instead of nextMove
const nextMove = this.moveInterpreter.nextMove;
const nextMove = this.moveInterpreter.nextMove!;
return this.handlePairedMove(this.move.parameters.who, ({ id, startPos, around, withId, withPos }) => {
// TODO swing can start from non-circle positions.