contra-renderer/www/js/moves/hey.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

339 lines
14 KiB
TypeScript

import { DancerIdentity } from "../danceCommon.js";
import { SemanticPosition, PositionKind, ShortLinesPosition, CirclePosition, CircleSide, Facing } from "../interpreterCommon.js";
import { Move } from "../libfigureMapper.js";
import { LowLevelMove, SemanticAnimationKind } from "../lowLevelMove.js";
import { Hand } from "../rendererConstants.js";
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
import { dancerIsPair } from "../libfigure/util.js";
const moveName: Move["move"] = "hey";
class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName> {
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
// Needed for inner functions... that probably should be methods.
const move = this.move;
type HeyStep = {
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut" | "Ricochet",
endPosition: SemanticPosition,
}
if (this.move.parameters.dir !== "across") {
throw new Error("Unsupported hey direction: " + this.move.parameters.dir);
}
let heyParts: number;
switch (this.move.parameters.until) {
case "half":
heyParts = 4;
break;
case "full":
heyParts = 8;
break;
default:
throw new Error("Unsupported hey 'until': " + this.move.parameters.until);
}
const heyPartBeats: number = this.move.beats / heyParts;
// TODO is this right?
const firstPassInCenter: boolean = dancerIsPair(this.move.parameters.who);
const centerShoulder = firstPassInCenter === this.move.parameters.shoulder ? Hand.Right : Hand.Left;
const endsShoulder = centerShoulder.opposite();
function fixupHeyOtherPath(withoutOtherPath: Map<DancerIdentity, (LowLevelMove & { heyStep?: HeyStep })[]>): Map<DancerIdentity, LowLevelMove[]> {
const numSteps = withoutOtherPath.get(DancerIdentity.OnesLark)!.length;
for (let i = 0; i < numSteps; i++) {
for (const id of withoutOtherPath.keys()) {
const lowLevelMove = withoutOtherPath.get(id)![i];
if (lowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|| !lowLevelMove.heyStep
|| lowLevelMove.movementPattern.otherPath) {
continue;
}
const heyStepKind = lowLevelMove.heyStep.kind;
let foundPair = false;
for (const otherId of withoutOtherPath.keys()) {
const otherLowLevelMove = withoutOtherPath.get(otherId)![i];
if (id === otherId
|| otherLowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|| !otherLowLevelMove.heyStep
|| otherLowLevelMove.movementPattern.otherPath) {
continue;
}
const otherHeyStepKind = otherLowLevelMove.heyStep.kind;
if (heyStepKind === "CenterPass" && otherHeyStepKind === "CenterPass"
|| (lowLevelMove.startPosition.which.leftRightSide() === otherLowLevelMove.startPosition.which.leftRightSide()
&& (heyStepKind === "EndsPassIn" && otherHeyStepKind === "EndsPassOut"
|| heyStepKind === "EndsPassOut" && otherHeyStepKind === "EndsPassIn"))) {
lowLevelMove.movementPattern.otherPath = {
start: { ...otherLowLevelMove.startPosition, setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset },
end: { ...otherLowLevelMove.endPosition, setOffset: lowLevelMove.endPosition.setOffset, lineOffset: lowLevelMove.endPosition.lineOffset },
}
otherLowLevelMove.movementPattern.otherPath = {
start: { ...lowLevelMove.startPosition, setOffset: otherLowLevelMove.startPosition.setOffset, lineOffset: otherLowLevelMove.startPosition.lineOffset },
end: { ...lowLevelMove.endPosition, setOffset: otherLowLevelMove.endPosition.setOffset, lineOffset: otherLowLevelMove.endPosition.lineOffset },
}
foundPair = true;
break;
}
}
if (!foundPair && heyStepKind === "EndsPassOut") {
// Then other is standing still.
const pos = {
...([...withoutOtherPath.values()]
.map(otherMoves => otherMoves[i])
.filter(m => m.movementPattern.kind === SemanticAnimationKind.StandStill
&& m.endPosition.which.leftRightSide() === lowLevelMove.endPosition.which.leftRightSide())
[0].endPosition),
setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset
}
lowLevelMove.movementPattern.otherPath = { start: pos, end: pos };
}
}
for (const id of withoutOtherPath.keys()) {
const lowLevelMove = withoutOtherPath.get(id)![i];
if (lowLevelMove.movementPattern.kind === SemanticAnimationKind.PassBy
&& !lowLevelMove.movementPattern.otherPath) {
throw new Error("Failed to fill in otherPath for " + id + " on hey step " + i);
}
}
}
// Object was mutated.
return withoutOtherPath;
}
return fixupHeyOtherPath(this.handleMove(({ id, startPos }) => {
const endsInCircle = startPos.kind === PositionKind.Circle;
function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
return {
beats: heyPartBeats,
// TODO use circle positions on ends? ... unless hey ends in a box the gnat or similar...
endPosition: heyStep.endPosition,
movementPattern: heyStep.kind === "StandStill" ? {
kind: SemanticAnimationKind.StandStill,
} : heyStep.kind === "Loop" ? {
// TODO Loop should probably be its own kind? Or RotateAround?
kind: SemanticAnimationKind.Linear,
minRotation: endsShoulder === Hand.Right ? +180 : -180,
} : heyStep.kind === "Ricochet" ? {
// TODO This is a hack.
kind: SemanticAnimationKind.PassBy,
around: heyStep.endPosition.which.leftRightSide(),
withHands: false,
otherPath: "Swap",
facing: "Start",
side: endsShoulder,
} : {
kind: SemanticAnimationKind.PassBy,
around: heyStep.kind === "CenterPass" ? "Center" : heyStep.endPosition.which.leftRightSide(),
withHands: false,
side: heyStep.kind === "CenterPass" ? centerShoulder : endsShoulder,
facing: "Start",
otherPath: undefined!, // Placeholder, fixup later.
},
heyStep,
};
}
function continueHey(prevStep: HeyStep, stepsLeft: number, beenInCenter: boolean): HeyStep {
// TODO Not sure why type checker requires rechecking this here.
if (move.move !== "hey") throw new Error("Unreachable.");
// Continuing hey so everyone is either passing (in center or on ends) or looping on ends.
if (prevStep.endPosition.kind === PositionKind.Circle) {
if (prevStep.endPosition.facing === prevStep.endPosition.which.facingAcross()) {
if (stepsLeft === 0) {
return {
kind: "StandStill",
endPosition: prevStep.endPosition,
}
}
return {
kind: "EndsPassIn",
endPosition: {
kind: PositionKind.ShortLines,
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.MiddleLeft : ShortLinesPosition.MiddleRight,
facing: prevStep.endPosition.which.facingAcross(),
setOffset: prevStep.endPosition.setOffset,
lineOffset: prevStep.endPosition.lineOffset,
},
}
}
else {
if (stepsLeft === 1 && !endsInCircle) {
return {
kind: "Loop",
endPosition: {
kind: PositionKind.ShortLines,
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.FarLeft : ShortLinesPosition.FarRight,
facing: prevStep.endPosition.which.facingAcross(),
setOffset: prevStep.endPosition.setOffset,
lineOffset: prevStep.endPosition.lineOffset,
},
}
}
return {
kind: "Loop",
endPosition: {
...prevStep.endPosition,
which: prevStep.endPosition.which.swapUpAndDown(),
facing: prevStep.endPosition.which.facingAcross()
},
}
}
}
else if (prevStep.endPosition.kind === PositionKind.ShortLines) {
const isFacingSide = prevStep.endPosition.facing === prevStep.endPosition.which.facingSide();
const inMiddle = prevStep.endPosition.which.isMiddle();
if (!inMiddle && !isFacingSide) {
return {
kind: "Loop",
endPosition: { ...prevStep.endPosition, facing: prevStep.endPosition.which.facingSide() },
}
} else if (inMiddle && isFacingSide) {
return {
kind: "EndsPassOut",
endPosition: {
...prevStep.endPosition,
kind: PositionKind.Circle,
which: prevStep.endPosition.which.isLeft()
? (endsShoulder === Hand.Right ? CirclePosition.TopLeft : CirclePosition.BottomLeft)
: (endsShoulder === Hand.Right ? CirclePosition.BottomRight : CirclePosition.TopRight),
},
}
}
else if (!isFacingSide) {
const rico = inCenterFirst
? beenInCenter
? move.parameters.rico3
: move.parameters.rico1
: beenInCenter
? move.parameters.rico4
: move.parameters.rico2;
if (rico) {
const onLeftSide = prevStep.endPosition.which.isLeft();
return {
kind: "Ricochet",
endPosition: {
...prevStep.endPosition,
kind: PositionKind.Circle,
which: CirclePosition.fromSides(prevStep.endPosition.which.leftRightSide(),
// TODO might be swapped
(endsShoulder === Hand.Left) === onLeftSide ? CircleSide.Top : CircleSide.Bottom),
facing: onLeftSide ? Facing.Left : Facing.Right,
}
}
} else {
return {
kind: "CenterPass",
endPosition: {
...prevStep.endPosition,
which: prevStep.endPosition.which.swapSides()
},
}
}
}
else {
return {
kind: inMiddle ? "EndsPassOut" : "EndsPassIn",
endPosition: {
...prevStep.endPosition,
which: prevStep.endPosition.which.swapOnSide()
},
}
}
} else {
throw new Error("Unexpected PositionKind: " + (<any>prevStep.endPosition).kind);
}
}
const inCenterFirst = firstPassInCenter && this.findPairOpposite(this.move.parameters.who, id) !== null
|| this.move.parameters.who2 && this.findPairOpposite(this.move.parameters.who2, id) !== null;
let firstHeyStep: HeyStep;
let startingPos: SemanticPosition;
if (firstPassInCenter) {
if (startPos.kind !== PositionKind.Circle) {
throw new Error("Hey starting in center not from circle is unsupported.");
}
startingPos = {
kind: startPos.kind,
which: startPos.which,
facing: startPos.which.isLeft() ? Facing.Right : Facing.Left,
setOffset: startPos.setOffset,
lineOffset: startPos.lineOffset,
};
if (inCenterFirst) {
if (this.move.parameters.rico1) {
firstHeyStep = {
kind: "Ricochet",
endPosition: {
kind: PositionKind.Circle,
which: startPos.which.swapUpAndDown(),
facing: startPos.which.facingOut(),
setOffset: startPos.setOffset,
lineOffset: startPos.lineOffset,
}
};
} else {
firstHeyStep = {
kind: "CenterPass",
endPosition: {
kind: PositionKind.ShortLines,
which: startPos.which.isLeft() ? ShortLinesPosition.MiddleRight : ShortLinesPosition.MiddleLeft,
facing: startingPos.facing,
setOffset: startPos.setOffset,
lineOffset: startPos.lineOffset,
}
};
}
} else {
firstHeyStep = {
kind: "StandStill",
endPosition: startingPos,
}
}
} else {
if (startPos.kind !== PositionKind.ShortLines) {
throw new Error("Hey with first pass on ends must start approximately in short lines.");
}
const startFacing = startPos.which.facingSide();
startingPos = {
kind: startPos.kind,
which: startPos.which,
facing: startFacing,
setOffset: startPos.setOffset,
lineOffset: startPos.lineOffset,
};
firstHeyStep = {
kind: startingPos.which.isMiddle() ? "EndsPassOut" : "EndsPassIn",
endPosition: { ...startingPos, which: startPos.which.swapOnSide() },
}
}
const heySteps: HeyStep[] = [firstHeyStep];
let beenInCenter = firstHeyStep.kind === "CenterPass" || firstHeyStep.kind === "Ricochet";
for (let i = 1; i < heyParts; i++) {
const isLast = i === heyParts - 1;
const nextHeyStep = continueHey(heySteps[i - 1], heyParts - i - 1, beenInCenter);
beenInCenter ||= nextHeyStep.kind === "CenterPass" || nextHeyStep.kind === "Ricochet";
heySteps.push(nextHeyStep);
}
return this.combine(heySteps.map(heyStepToPartialLowLevelMove), { ...startingPos, hands: undefined });
}));
}
}
class Hey extends MoveInterpreter<typeof moveName> {
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
return new HeySingleVariant(this, startingPos);
}
}
moveInterpreters.set(moveName, Hey);