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 { 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([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.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 { buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter { return new SwingSingleVariant(this, startingPos); } } moveInterpreters.set(moveName, Swing);