contra-renderer/www/js/moves/hey.ts

360 lines
15 KiB
TypeScript

import { DancerIdentity } from "../danceCommon.js";
import { SemanticPosition, PositionKind, ShortLinesPosition, CirclePosition, CircleSide, Facing } from "../interpreterCommon.js";
import { LowLevelMove, SemanticAnimationKind } from "../lowLevelMove.js";
import { Hand } from "../rendererConstants.js";
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
import { dancerIsPair } from "../libfigure/util.js";
const moveName = "hey";
type HeyStep = {
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut" | "Ricochet",
endPosition: SemanticPosition,
}
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;
}
class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName> {
heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
return {
beats: this.moveInterpreter.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: this.moveInterpreter.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: this.moveInterpreter.endsShoulder,
} : {
kind: SemanticAnimationKind.PassBy,
around: heyStep.kind === "CenterPass" ? "Center" : heyStep.endPosition.which.leftRightSide(),
withHands: false,
side: heyStep.kind === "CenterPass" ? this.moveInterpreter.centerShoulder : this.moveInterpreter.endsShoulder,
facing: "Start",
otherPath: undefined!, // Placeholder, fixup later.
},
heyStep,
};
}
continueHey(prevStep: HeyStep, stepsLeft: number,
{ beenInCenter, endsInCircle, inCenterFirst }:
{ beenInCenter: boolean, endsInCircle: boolean, inCenterFirst: boolean }): HeyStep {
// TODO Not sure why type checker requires rechecking this here.
if (this.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()
? (this.moveInterpreter.endsShoulder === Hand.Right ? CirclePosition.TopLeft : CirclePosition.BottomLeft)
: (this.moveInterpreter.endsShoulder === Hand.Right ? CirclePosition.BottomRight : CirclePosition.TopRight),
},
}
}
else if (!isFacingSide) {
const rico = inCenterFirst
? beenInCenter
? this.move.parameters.rico3
: this.move.parameters.rico1
: beenInCenter
? this.move.parameters.rico4
: this.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
(this.moveInterpreter.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);
}
}
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
return fixupHeyOtherPath(this.handleMove(({ id, startPos }) => {
const endsInCircle = startPos.kind === PositionKind.Circle;
const inCenterFirst = this.moveInterpreter.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 (this.moveInterpreter.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 < this.moveInterpreter.heyParts; i++) {
const nextHeyStep = this.continueHey(heySteps[i - 1], this.moveInterpreter.heyParts - i - 1, {beenInCenter, endsInCircle, inCenterFirst});
beenInCenter ||= nextHeyStep.kind === "CenterPass" || nextHeyStep.kind === "Ricochet";
heySteps.push(nextHeyStep);
}
return this.combine(heySteps.map(s => this.heyStepToPartialLowLevelMove(s)), { ...startingPos, hands: undefined });
}));
}
}
class Hey extends MoveInterpreter<typeof moveName> {
public readonly heyParts: number;
public readonly heyPartBeats: number;
public readonly firstPassInCenter: boolean;
public readonly centerShoulder: Hand;
public readonly endsShoulder: Hand;
constructor(args: MoveInterpreterCtorArgs<typeof moveName>) {
super(args);
if (this.move.parameters.dir !== "across") {
throw new Error("Unsupported hey direction: " + this.move.parameters.dir);
}
if (typeof this.move.parameters.until === "string") {
switch (this.move.parameters.until) {
case "half":
this.heyParts = 4;
break;
case "full":
this.heyParts = 8;
break;
// TODO Are these right? Can it sometimes be 1 or 3 instead of 2?
case "less than half":
this.heyParts = 2;
break;
case "between half and full":
this.heyParts = 6;
default:
throw new Error("Unsupported hey 'until': " + this.move.parameters.until);
}
}
else {
// TODO Is this actually this simple?
this.heyParts = this.move.parameters.until.time === 1 ? 2 : 6;
//throw new Error("Unsupported hey 'until': " + this.move.parameters.until.dancer + " time " + this.move.parameters.until.time);
}
this.heyPartBeats = this.move.beats / this.heyParts;
// TODO is this right?
this.firstPassInCenter = dancerIsPair(this.move.parameters.who);
this.centerShoulder = this.firstPassInCenter === this.move.parameters.shoulder ? Hand.Right : Hand.Left;
this.endsShoulder = this.centerShoulder.opposite();
}
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
return new HeySingleVariant(this, startingPos);
}
}
moveInterpreters.set(moveName, Hey);