import { SemanticPosition, PositionKind, CircleSide, Facing, CirclePosition, LongLines, HandConnection, DancerDistance } from "../interpreterCommon.js"; import { SemanticAnimationKind } from "../lowLevelMove.js"; import { Hand } from "../rendererConstants.js"; import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js"; type allemandeMoves = "allemande" | "allemande orbit" | "gyre"; class AllemandeSingleVariant extends SingleVariantMoveInterpreter { moveAsLowLevelMoves(): LowLevelMovesForAllDancers { // Need to store this locally so checking move.move restricts move.parameters. const move = this.move; for (const [id, pos] of this.startingPos.entries()) { if (this.findPairOpposite(this.move.parameters.who, id) === null) { if (pos.dancerDistance && pos.dancerDistance !== DancerDistance.Normal) { throw new Error("Dancers not involved in allemande must start at normal dancer distance."); } if (pos.longLines) { throw new Error("Dancers not involved in allemande must start not in long lines."); } } } return this.handlePairedMove(this.move.parameters.who, ({ startPos, around, withId, withPos }) => { if (startPos.kind === PositionKind.ShortLines && withPos.kind === PositionKind.ShortLines && startPos.which.leftRightSide() != withPos.which.leftRightSide() && (!startPos.which.isMiddle() || !withPos.which.isMiddle())) { throw new Error("Allemande in short lines must either be on same side of set or in the middle."); } let endPosition: SemanticPosition = {...startPos, dancerDistance: undefined, longLines: undefined, balance: undefined }; let startingPos = {...startPos }; if (this.moveInterpreter.swap) { // TODO This was more complicated. Is this wrong? endPosition = {...withPos, dancerDistance: undefined, longLines: undefined, balance: undefined }; } else if (this.moveInterpreter.intoWave) { if (startPos.kind === PositionKind.ShortLines) { if (around === CircleSide.Left || around === CircleSide.Right) { // Fix startPos if necessary. Needed because pass through always swaps but sometimes shouldn't. let startWhich = startPos.which; if ((startPos.facing === Facing.Up || startPos.facing === Facing.Down) && ((this.moveInterpreter.byHandOrShoulder === Hand.Right) !== (startPos.facing === Facing.Up) !== startPos.which.isLeftOfSide())) { startWhich = startPos.which.swapOnSide() startingPos = { ...startPos, which: startWhich, }; } const endWhich = CirclePosition.fromSides(startingPos.which.leftRightSide(), startWhich.isLeftOfSide() !== (this.moveInterpreter.byHandOrShoulder === Hand.Right) !== (this.moveInterpreter.intoWavePositions === 1) ? CircleSide.Top : CircleSide.Bottom); endPosition = { kind: PositionKind.Circle, which: endWhich, facing: (startingPos.facing === Facing.Up) === (this.moveInterpreter.intoWavePositions === 1) ? endWhich.facingAcross() : endWhich.facingOut(), setOffset: startingPos.setOffset, lineOffset: startingPos.lineOffset, } } else { throw new Error("Allemande from short lines to line line in middle unsupported."); } } else { if (around === "Center") { const startCenter = startPos.longLines === LongLines.Center; const endWhich = startPos.which.circleRight(this.moveInterpreter.intoWavePositions); endPosition = { kind: PositionKind.Circle, which: endWhich, facing: startPos.which.facingOut(), longLines: startCenter ? undefined : LongLines.Center, setOffset: startPos.setOffset, lineOffset: startPos.lineOffset, } } else { const endWhich = startPos.which.toShortLines(this.moveInterpreter.intoWavePositions === 1 ? Hand.Right : Hand.Left); endPosition = { kind: PositionKind.ShortLines, which: endWhich, facing: endWhich.isLeftOfSide() === (this.moveInterpreter.byHandOrShoulder === Hand.Left) ? Facing.Up : Facing.Down, setOffset: startPos.setOffset, lineOffset: startPos.lineOffset, } } } } return this.combine([ { beats: this.move.beats, endPosition, movementPattern: { kind: SemanticAnimationKind.RotateAround, minAmount: this.moveInterpreter.byHandOrShoulder === Hand.Right ? this.moveInterpreter.allemandeCircling : -this.moveInterpreter.allemandeCircling, around, byHand: move.move === "allemande" || move.move === "allemande orbit" ? this.moveInterpreter.byHandOrShoulder : undefined, close: true, }, }, ], { ...startingPos, hands: startPos.hands && move.move !== "gyre" ? new Map([...startPos.hands.entries()].filter(([h, c]) => h === this.moveInterpreter.byHandOrShoulder)) : undefined }); }, move.move !== "allemande orbit" ? undefined : ({ id, startPos }) => { const startingPos: SemanticPosition = { ...startPos, hands: undefined, balance: undefined, dancerDistance: undefined, } let endPosition: SemanticPosition; if (this.moveInterpreter.orbitSwap) { if (startingPos.kind === PositionKind.Circle) { endPosition = { ...startingPos, which: startingPos.which.swapDiagonal(), facing: startingPos.which.isLeft() ? Facing.Left : Facing.Right, } } else { endPosition = { ...startingPos, which: startingPos.which.swapSides(), facing: startingPos.which.isLeft() ? Facing.Left : Facing.Right, } } } else { endPosition = startingPos; } return this.combine([ { beats: move.beats, endPosition, movementPattern: { kind: SemanticAnimationKind.RotateAround, // Orbit is opposite direction of allemande. minAmount: this.moveInterpreter.byHandOrShoulder === Hand.Right ? -this.moveInterpreter.orbitCircling : +this.moveInterpreter.orbitCircling, around: "Center", byHand: undefined, close: false, }, }, ], startingPos); }); } } class Allemande extends MoveInterpreter { public readonly allemandeCircling: number; public readonly orbitCircling: number; public readonly byHandOrShoulder: Hand; public readonly swap: boolean; public readonly orbitSwap: boolean; public readonly returnToStart: boolean; public readonly intoWave: boolean; public readonly intoWavePositions: number; constructor(args: MoveInterpreterCtorArgs) { super(args); this.allemandeCircling = this.move.move === "allemande orbit" ? this.move.parameters.circling1 : this.move.parameters.circling; this.byHandOrShoulder = (this.move.move === "gyre" ? this.move.parameters.shoulder : this.move.parameters.hand) ? Hand.Right : Hand.Left; // TODO Not sure if this is right. this.swap = this.allemandeCircling % 360 === 180; this.returnToStart = this.allemandeCircling % 360 === 0; this.intoWave = !this.swap && !this.returnToStart && this.allemandeCircling % 90 == 0; this.intoWavePositions = !this.intoWave ? 0 : (this.allemandeCircling % 360 === 90) === (this.byHandOrShoulder === Hand.Left) ? +1 : -1; if (!this.swap && !this.returnToStart && !this.intoWave) { // TODO Support allemande that's not a swap or no-op. throw "Unsupported allemande circle amount: " + this.allemandeCircling; } if (this.move.move === "allemande orbit") { this.orbitCircling = this.move.parameters.circling2; this.orbitSwap = this.orbitCircling % 360 === 180; if (!this.orbitSwap && this.orbitCircling % 360 !== 0) { // TODO Support allemande that's not a swap or no-op. throw "Unsupported allemande orbit amount: " + this.orbitCircling; } } else { this.orbitCircling = 0; this.orbitSwap = false; } } override allowStartingClose(): boolean { return true; } override allowStartingLongLines(): boolean { return true; } buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter { return new AllemandeSingleVariant(this, startingPos); } } moveInterpreters.set("allemande", Allemande); moveInterpreters.set("allemande orbit", Allemande); moveInterpreters.set("gyre", Allemande);