contra-renderer/www/js/interpreter.ts
Daniel Perelman 9fbf7d18ac [WIP] Refactor to split interpreter into one file per move.
Currently just copied over the existing code and applied the quick
fixes to get it to compile. Each move should be refactored to be handle
its parameters earlier where applicable. But variants support should
probably be added first so both refactors can happen together.
2023-10-15 05:25:06 -07:00

213 lines
8.8 KiB
TypeScript

import * as animation from "./animation.js";
import { CoupleRole, DanceRole, DancerIdentity } from "./danceCommon.js";
import * as common from "./danceCommon.js";
import { Hand, setDistance } from "./rendererConstants.js";
import { nameLibFigureParameters, Move, chooser_pairz } from "./libfigureMapper.js";
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 all individual move files.
// ls [a-z]*.js | sed 's@.*@import "./moves/\0";@'
import "./moves/allemande.js";
import "./moves/balance.js";
import "./moves/balanceTheRing.js";
import "./moves/boxCirculate.js";
import "./moves/boxTheGnat.js";
import "./moves/butterflyWhirl.js";
import "./moves/californiaTwirl.js";
import "./moves/chain.js";
import "./moves/circle.js";
import "./moves/custom.js";
import "./moves/doSiDo.js";
import "./moves/downTheHall.js";
import "./moves/formAnOceanWave.js";
import "./moves/formLongWaves.js";
import "./moves/giveTake.js";
import "./moves/hey.js";
import "./moves/longLines.js";
import "./moves/madRobin.js";
import "./moves/passBy.js";
import "./moves/passThrough.js";
import "./moves/petronella.js";
import "./moves/promenade.js";
import "./moves/pullByDancers.js";
import "./moves/revolvingDoor.js";
import "./moves/rightLeftThrough.js";
import "./moves/rollAway.js";
import "./moves/roryOMore.js";
import "./moves/slice.js";
import "./moves/slideAlongSet.js";
import "./moves/star.js";
import "./moves/starPromenade.js";
import "./moves/swing.js";
import "./moves/turnAlone.js";
import "./moves/upTheHall.js";
function moveAsLowLevelMoves({ move, nextMove, startingPos, numProgessions }: { move: Move; nextMove: Move; startingPos: Map<DancerIdentity, SemanticPosition>; numProgessions: number; }): Map<DancerIdentity, LowLevelMove[]> {
const moveInterpreter = moveInterpreters.get(move.move) ?? errorMoveInterpreterCtor;
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);
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];
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 });
const prevMove = res.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);
}
}
}
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,
});
}
}
if (move.progression) numProgessions++;
}
try {
const progression = animateFromLowLevelMoves(res).progression;
const progressionInSets = progression.y / setDistance;
// fixup end positions to match start of next move
// TODO Handle progression.
for (const [id, lowLevelMoves] of res.entries()) {
for (let i = 0; i < lowLevelMoves.length - 1; i++) {
if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined";
lowLevelMoves[i].endPosition = lowLevelMoves[i + 1].startPosition;
if (!lowLevelMoves[i].endPosition) throw "endPosition is undefined now";
if (lowLevelMoves[i].movementPattern.kind === SemanticAnimationKind.StandStill) {
lowLevelMoves[i].startPosition = lowLevelMoves[i].endPosition;
if (i > 0) {
lowLevelMoves[i - 1].endPosition = lowLevelMoves[i].startPosition;
}
}
}
// If progression isn't detected properly, do nothing.
if (progressionInSets === 0) {
lowLevelMoves[lowLevelMoves.length - 1].interpreterError = "No progression detected. Not lining up end with start of dance.";
} else {
const startPos = lowLevelMoves[0].startPosition;
lowLevelMoves[lowLevelMoves.length - 1].endPosition = {
...lowLevelMoves[0].startPosition,
// progressed
setOffset: (startPos.setOffset ?? 0) + (id.coupleRole == CoupleRole.Ones ? 1 : -1) * progressionInSets,
};
}
}
return res;
}
catch (ex) {
res.get(DancerIdentity.OnesLark)![0].interpreterError = "Error detecting progression: " + (ex instanceof Error ? ex.message : ex);
return res;
}
}
function StartingPosForFormation(formation: common.StartFormation, dance?: ContraDBDance): Map<DancerIdentity, SemanticPosition> {
const preamble = dance?.preamble ?? "";
if (preamble.includes("Starts in short waves, right hands to neighbors")) {
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
([id, pos]) => ([id, {
kind: PositionKind.ShortLines,
which: pos.which.toShortLines(Hand.Left),
facing: pos.which.isTop() ? Facing.Down : Facing.Up,
}])
));
} else if (preamble.includes("face down the hall in lines of 4, 1s splitting the 2s")) {
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
([id, pos]) => ([id, {
kind: PositionKind.ShortLines,
which: pos.which.unfoldToShortLines(CircleSide.Top),
facing: Facing.Down,
}])
));
}
switch (formation) {
case "improper":
return handsFourImproper;
case "Becket":
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
([id, pos]) => ([id, {...pos, which: pos.which.circleLeft(1)}])
));
case "Becket ccw":
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
([id, pos]) => ([id, {...pos, which: pos.which.circleRight(1)}])
));
case "Sawtooth Becket":
// Dancers start becket, then slide one person to the right. https://contradb.com/dances/848
// TODO Not sure this is right.
return new Map<common.DancerIdentity, SemanticPosition>([
[DancerIdentity.OnesLark, {
kind: PositionKind.Circle,
which: CirclePosition.BottomLeft,
facing: Facing.CenterOfCircle,
hands: handsInCircle,
}],
[DancerIdentity.OnesRobin, {
kind: PositionKind.Circle,
which: CirclePosition.TopLeft,
facing: Facing.CenterOfCircle,
hands: handsInCircle,
}],
[DancerIdentity.TwosLark, {
kind: PositionKind.Circle,
which: CirclePosition.BottomRight,
facing: Facing.CenterOfCircle,
hands: handsInCircle,
}],
[DancerIdentity.TwosRobin, {
kind: PositionKind.Circle,
which: CirclePosition.TopRight,
facing: Facing.CenterOfCircle,
hands: handsInCircle,
setOffset: 1,
}],
]);
}
}
export let mappedDance: Move[];
export let interpretedDance: Map<DancerIdentity, LowLevelMove[]>;
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));
interpretedAnimation = animateFromLowLevelMoves(interpretedDance);
return interpretedAnimation;
}