import { CoupleRole, DanceRole } from "../danceCommon.js"; import { LongLines, CircleSide, SemanticPosition, CirclePosition, Facing, PositionKind, HandConnection, HandTo, ShortLinesPosition, handsInLine, BalanceWeight, DancerDistance, isFacingUpOrDown, isLeftRightCircleSide } from "../interpreterCommon.js"; import { SemanticAnimationKind } from "../lowLevelMove.js"; import { Hand } from "../rendererConstants.js"; import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, Variant, VariantCollection, moveInterpreters } from "./_moveInterpreter.js"; const moveName = "swing"; /* Possible swings: * - default: start on left/right side, end facing across * - give and take: start in one corner, end facing across * - contra corners: just one pair, start on top/bottom side, end facing in to new circle * - to short lines: start on left/right side, end facing up/down in short lines * - same role: larks/robins go into center, end facing out * - join short lines: just one pair in center, joins hands in short lines after * - ... ? */ interface SwingEnd { facing: "Across" | Facing.Up | Facing.Down, close: boolean, } function swingEndString(swingEnd: SwingEnd): string { return swingEnd.facing + (swingEnd.close ? "Close" : ""); } const swingEndValues: SwingEnd[] = [ { facing: "Across", close: false}, { facing: "Across", close: true}, { facing: Facing.Up, close: false}, { facing: Facing.Up, close: true}, { facing: Facing.Down, close: false}, { facing: Facing.Down, close: true}, ]; class SwingSingleVariant extends SingleVariantMoveInterpreter { moveAsVariants(previousMoveVariant: string): VariantCollection { const res = new Map(); for (const swingEnd of swingEndValues) { try { res.set(swingEndString(swingEnd), { lowLevelMoves: this.moveAsLowLevelMovesFacing(swingEnd), previousMoveVariant }); } catch { } } return res; } moveAsLowLevelMovesFacing(swingEnd: SwingEnd): LowLevelMovesForAllDancers { return this.handlePairedMove(this.move.parameters.who, ({ id, startPos, around, withId, withPos }) => { if (startPos.longLines && startPos.longLines !== LongLines.Near) { throw new Error(this.move.move + " shouldn't start with long lines " + startPos.longLines); } const afterTake = startPos.longLines === LongLines.Near || withPos.longLines === LongLines.Near; if (around === "Center" && swingEnd.facing === "Across") { throw new Error("Cannot end a swing around the center facing across."); } const toShortLines = around === "Center" || (isLeftRightCircleSide(around) && isFacingUpOrDown(swingEnd.facing)); const startWhich = startPos.which; const startPosition: SemanticPosition = { ...startPos, facing: isLeftRightCircleSide(around) ? (startWhich instanceof CirclePosition ? afterTake ? startPos.longLines === LongLines.Near ? startWhich.facingOut() : startWhich.facingAcross() : startWhich.facingUpOrDown() : startWhich.facingSide()) : startWhich.facingAcross(), }; 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; const endFacing = swingEnd.facing === "Across" ? startWhich.facingAcross() : swingEnd.facing; const endWhich = toShortLines && /*always true, just for type-checker*/isFacingUpOrDown(swingEnd.facing) ? ShortLinesPosition.fromSwing(around, swingRole, swingEnd.facing) : CirclePosition.fromSwing(/*always true, just for type-checker*/around, swingRole, swingEnd.facing) const endHands = toShortLines ? handsInLine({ wavy: false, which: endWhich, facing: endFacing }) : new Map([swingRole === DanceRole.Lark ? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }] : [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]); const partialEndPosition = { ...startPos, facing: endFacing, balance: undefined, dancerDistance: swingEnd.close ? DancerDistance.Compact : undefined, longLines: undefined, hands: endHands, } let endPosition: SemanticPosition = toShortLines ? { ...partialEndPosition, kind: PositionKind.ShortLines, which: endWhich, } : { ...partialEndPosition, kind: PositionKind.Circle, which: endWhich, } const swing: PartialLowLevelMove = { beats: this.moveInterpreter.swingBeats, endPosition: endPosition, movementPattern: { kind: SemanticAnimationKind.Swing, minAmount: 360, around, endFacing, swingRole, afterTake, }, }; switch (this.move.parameters.prefix) { case "none": return this.combine([swing,], startPosition); case "balance": const startWithBalHands = { ...startPosition, hands: new Map([ [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: this.moveInterpreter.balancePartBeats, startPosition: startWithBalHands, endPosition: balForwardPos, movementPattern: { kind: SemanticAnimationKind.Linear, }, }, prevEnd => ({ beats: this.moveInterpreter.balancePartBeats, endPosition: { ...prevEnd, balance: BalanceWeight.Backward, }, movementPattern: { kind: SemanticAnimationKind.Linear, }, }), swing, ], startPosition); case "meltdown": return this.combine([ prevEnd => ({ beats: this.moveInterpreter.meltdownBeats, endPosition: { ...prevEnd, dancerDistance: DancerDistance.Compact }, movementPattern: { kind: SemanticAnimationKind.RotateAround, minAmount: 360, around, byHand: undefined, close: true, }, }), swing, ], startPosition); } }); } } class Swing extends MoveInterpreter { public readonly swingBeats: number; public readonly balancePartBeats: number; public readonly meltdownBeats: number; constructor(args: MoveInterpreterCtorArgs) { super(args); switch(this.move.parameters.prefix) { case "none": this.balancePartBeats = 0; this.meltdownBeats = 0; this.swingBeats = this.move.beats; break; case "balance": this.meltdownBeats = 0; this.balancePartBeats = this.move.beats > 8 ? (this.move.beats - 8) / 2 : this.move.beats > 4 ? 2 : this.move.beats / 4; this.swingBeats = this.move.beats - this.balancePartBeats * 2; break; case "meltdown": this.balancePartBeats = 0; this.meltdownBeats = this.move.beats >= 8 ? 4 : this.move.beats / 2; this.swingBeats = this.move.beats - this.meltdownBeats; break; default: throw new Error ("Unknown swing prefix: " + this.move.parameters.prefix); } this.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 })(); } override allowStartingClose(): boolean { return true; } override allowStartingLongLines(): boolean { // Long lines is used for LongLines.Near after give and take. return true; } buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter { return new SwingSingleVariant(this, startingPos); } } moveInterpreters.set(moveName, Swing);