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

216 lines
9.1 KiB
TypeScript

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<Allemande, allemandeMoves> {
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<Hand, HandConnection>([...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<allemandeMoves> {
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<allemandeMoves>) {
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);