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

187 lines
8.3 KiB
TypeScript

import { CoupleRole, DanceRole } from "../danceCommon.js";
import { LongLines, CircleSide, SemanticPosition, CirclePosition, Facing, PositionKind, HandConnection, HandTo, ShortLinesPosition, handsInLine, BalanceWeight, DancerDistance } from "../interpreterCommon.js";
import { Move } from "../libfigureMapper.js";
import { SemanticAnimationKind } from "../lowLevelMove.js";
import { Hand } from "../rendererConstants.js";
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
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;
return this.handlePairedMove(this.move.parameters.who, ({ id, startPos, around, withId, withPos }) => {
// TODO swing can start from non-circle positions.
// TODO swing end is only in notes / looking at next move.
// TODO better way to detect swing end?
// TODO more structured way to do this than enumerating next moves here?
// maybe instead of nextMove an optional endPosition for fixing up positions?
// ... but then every move would have to handle that...
const afterTake = startPos.longLines === LongLines.Near || withPos.longLines === LongLines.Near;
const toShortLines = nextMove.move === "down the hall" || nextMove.move === "up the hall";
const endFacingAcross = (around === CircleSide.Left || around === CircleSide.Right) && !toShortLines;
const startWhich = startPos.which;
const startPosition: SemanticPosition = {
...startPos,
facing: around === CircleSide.Left || CircleSide.Right
? (startWhich instanceof CirclePosition
? afterTake
? startPos.longLines === LongLines.Near ? startWhich.facingOut() : startWhich.facingAcross()
: (startWhich.topBottomSide() === CircleSide.Bottom ? Facing.Up : Facing.Down)
: startWhich.facingSide())
: (startWhich.isLeft() ? Facing.Right : Facing.Left),
};
const swingRole = id.danceRole != withId.setIdentity.danceRole
? id.danceRole
// Make some arbitrary choice for same-role swings
: id.coupleRole !== withId.setIdentity.coupleRole
? (id.coupleRole === CoupleRole.Ones ? DanceRole.Lark : DanceRole.Robin)
: withId.relativeSet !== 0
? (withId.relativeSet > 0 ? DanceRole.Lark : DanceRole.Robin)
: withId.relativeLine !== 0
? (withId.relativeLine > 0 ? DanceRole.Lark : DanceRole.Robin)
: /* should be unreachable as this means withId is equal to id */ DanceRole.Lark;
// TODO This assumes swing around right/left, not center or top/bottom.
let endPosition: SemanticPosition;
if (endFacingAcross) {
endPosition = {
...startPos,
kind: PositionKind.Circle,
which: startWhich instanceof CirclePosition
? (startWhich.isOnLeftLookingAcross() === (swingRole === DanceRole.Lark)
? startWhich
: startWhich.swapUpAndDown())
: (startWhich.isLeft()
? (swingRole === DanceRole.Lark ? CirclePosition.BottomLeft : CirclePosition.TopLeft)
: (swingRole === DanceRole.Lark ? CirclePosition.TopRight : CirclePosition.BottomRight)),
facing: startWhich.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
balance: undefined,
dancerDistance: undefined,
longLines: undefined,
hands: new Map<Hand, HandConnection>([swingRole === DanceRole.Lark
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]),
};
} else if (toShortLines) {
const endFacing = nextMove.move === "down the hall" !== (nextMove.parameters.facing === "backward")
? Facing.Down
: Facing.Up;
const endWhich = startWhich.isLeft()
? ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.FarLeft : ShortLinesPosition.MiddleLeft)
: ((endFacing === Facing.Down) === (swingRole === DanceRole.Lark) ? ShortLinesPosition.MiddleRight : ShortLinesPosition.FarRight)
endPosition = {
...startPos,
kind: PositionKind.ShortLines,
which: endWhich,
facing: endFacing,
balance: undefined,
dancerDistance: undefined,
longLines: undefined,
hands: handsInLine({ wavy: false, which: endWhich, facing: endFacing }),
};
}
else {
// TODO Need to figure out the logic of knowing if this should be facing up or down.
// Probably based on knowing Ones vs. Twos? Also then the not-participating-dancers need their
// "standing still" to update that they are in a new set...
//throw new Error("Swing to new circle currently unsupported.");
endPosition = {
// end not facing across or in short lines, so transitioning to new circle like in many contra corners dances.
...startPos,
};
}
const swingBeats = this.move.parameters.prefix === "none" ? this.move.beats
: this.move.parameters.prefix === "balance"
? this.move.beats > 8 ? 8 : this.move.beats - 4
: this.move.parameters.prefix === "meltdown"
? this.move.beats - 4
: (() => { throw "Unknown swing prefix: " + this.move.parameters.prefix })();
const swing: PartialLowLevelMove = {
beats: swingBeats,
endPosition: endPosition,
movementPattern: {
kind: SemanticAnimationKind.Swing,
minAmount: 360,
around,
endFacing: startWhich.leftRightSide() === CircleSide.Left ? Facing.Right : Facing.Left,
swingRole,
afterTake,
},
};
switch (this.move.parameters.prefix) {
case "none":
return this.combine([swing,], startPosition);
case "balance":
// TODO Right length for balance?
const balancePartBeats = this.move.beats > 8 ? (this.move.beats - 8) / 2 : 2;
const startWithBalHands = {
...startPosition,
hands: new Map<Hand, HandConnection>([
[Hand.Left, { to: HandTo.DancerForward, hand: Hand.Right }],
[Hand.Right, { to: HandTo.DancerForward, hand: Hand.Left }],
]),
};
const balForwardPos = startWithBalHands.kind === PositionKind.Circle
? {
...startWithBalHands,
balance: BalanceWeight.Forward,
dancerDistance: DancerDistance.Compact,
}
: {
...startWithBalHands,
balance: BalanceWeight.Forward,
};
return this.combine([
{
beats: balancePartBeats,
startPosition: startWithBalHands,
endPosition: balForwardPos,
movementPattern: { kind: SemanticAnimationKind.Linear, },
},
prevEnd => ({
beats: balancePartBeats,
endPosition: {
...prevEnd,
balance: BalanceWeight.Backward,
},
movementPattern: { kind: SemanticAnimationKind.Linear, },
}),
swing,
], startPosition);
case "meltdown":
const meltdownBeats = 4; // TODO right number here?
return this.combine([
prevEnd => ({
beats: meltdownBeats,
endPosition: { ...prevEnd, dancerDistance: DancerDistance.Compact },
movementPattern: {
kind: SemanticAnimationKind.RotateAround,
minAmount: 360,
around,
byHand: undefined,
close: true,
},
}),
swing,
], startPosition);
}
});
}
}
class Swing extends MoveInterpreter<typeof moveName> {
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
return new SwingSingleVariant(this, startingPos);
}
}
moveInterpreters.set(moveName, Swing);