Compare commits
2 Commits
f182962365
...
fe481f37b5
Author | SHA1 | Date | |
---|---|---|---|
fe481f37b5 | |||
83dd2953e2 |
|
@ -219,24 +219,44 @@ function danceAsLowLevelMoves(moves: Move[], startingVariants: AllVariantsForMov
|
|||
}
|
||||
}
|
||||
|
||||
function improperShortWaves(slideTo: Hand) {
|
||||
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
|
||||
([id, pos]) => ([id, {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: pos.which.toShortLines(slideTo),
|
||||
facing: pos.which.facingUpOrDown(),
|
||||
}])
|
||||
));
|
||||
}
|
||||
|
||||
function improperUnfoldToShortLines(center: CircleSide.Bottom | CircleSide.Top) {
|
||||
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
|
||||
([id, pos]) => ([id, {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: pos.which.unfoldToShortLines(center),
|
||||
facing: center === CircleSide.Top ? Facing.Down : Facing.Up,
|
||||
}])
|
||||
));
|
||||
}
|
||||
|
||||
function improperLongWaves(facingOut: DanceRole) {
|
||||
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
|
||||
([id, pos]) => ([id, {
|
||||
...pos,
|
||||
facing: id.danceRole === facingOut ? pos.which.facingOut() : pos.which.facingAcross(),
|
||||
hands: handsInLine({ wavy: true, which: pos.which, facing: undefined })
|
||||
}])
|
||||
));
|
||||
}
|
||||
|
||||
function StartingPosForFormation(formation: common.StartFormation, dance?: ContraDBDance): Map<DancerIdentity, SemanticPosition> {
|
||||
const preamble = dance?.preamble ?? "";
|
||||
if (preamble.includes("Starts in short waves, right hands to neighbors")) {
|
||||
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
|
||||
([id, pos]) => ([id, {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: pos.which.toShortLines(Hand.Left),
|
||||
facing: pos.which.isTop() ? Facing.Down : Facing.Up,
|
||||
}])
|
||||
));
|
||||
return improperShortWaves(Hand.Left);
|
||||
} else if (preamble.includes("face down the hall in lines of 4, 1s splitting the 2s")) {
|
||||
return new Map<DancerIdentity, SemanticPosition>([...handsFourImproper.entries()].map(
|
||||
([id, pos]) => ([id, {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: pos.which.unfoldToShortLines(CircleSide.Top),
|
||||
facing: Facing.Down,
|
||||
}])
|
||||
));
|
||||
return improperUnfoldToShortLines(CircleSide.Top);
|
||||
} else if (preamble.includes("Long waves, larks face out")) {
|
||||
return improperLongWaves(DanceRole.Lark);
|
||||
}
|
||||
|
||||
switch (formation) {
|
||||
|
@ -284,12 +304,24 @@ function StartingPosForFormation(formation: common.StartFormation, dance?: Contr
|
|||
}
|
||||
|
||||
function startingVariantsForFormation(formation: common.StartFormation, dance?: ContraDBDance) : AllVariantsForMoveArgs {
|
||||
// TODO Handle various different starting position that all count as "improper" like short/long waves, etc.
|
||||
const pos = StartingPosForFormation(formation, dance);
|
||||
const defaultFormation = StartingPosForFormation(formation, dance);
|
||||
if (formation !== "improper" || defaultFormation !== handsFourImproper) {
|
||||
return new Map<string, MoveAsLowLevelMovesArgs>([
|
||||
["Initial", { startingPos: defaultFormation }]
|
||||
]);
|
||||
} else {
|
||||
// Handle various different starting position that all count as "improper" like short/long waves, etc.
|
||||
return new Map<string, MoveAsLowLevelMovesArgs>([
|
||||
["InitialCircle", { startingPos: handsFourImproper }],
|
||||
["InitialShortWavesLeft", { startingPos: improperShortWaves(Hand.Left) }],
|
||||
["InitialShortWavesRight", { startingPos: improperShortWaves(Hand.Right) }],
|
||||
["InitialShortLinesDown", { startingPos: improperUnfoldToShortLines(CircleSide.Top) }],
|
||||
["InitialShortLinesUp", { startingPos: improperUnfoldToShortLines(CircleSide.Bottom) }],
|
||||
["InitialLongWavesLarksOut", { startingPos: improperLongWaves(DanceRole.Lark) }],
|
||||
["InitialLongWavesRobinsOut", { startingPos: improperLongWaves(DanceRole.Robin) }],
|
||||
]);
|
||||
|
||||
return new Map<string, MoveAsLowLevelMovesArgs>([
|
||||
["Initial", { startingPos: pos }]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export let mappedDance: Move[];
|
||||
|
|
|
@ -343,13 +343,25 @@ export class ShortLinesPosition {
|
|||
}
|
||||
|
||||
public shift(dir: Hand, facing: Facing.Up | Facing.Down): ShortLinesPosition {
|
||||
const shift = (dir === Hand.Left) === (facing === Facing.Down) ? -1 : +1;
|
||||
const newNum = ShortLinesPosition.enumValueToNumber(this.enumValue) + shift;
|
||||
if (newNum < 0 || newNum > 3) {
|
||||
const { newPos, wrap } = this.shiftWithWrap(dir, facing);
|
||||
if (wrap) {
|
||||
throw new Error("Invalid shift: " + this + " facing " + facing + " to " + dir + ".");
|
||||
}
|
||||
|
||||
return newPos;
|
||||
}
|
||||
|
||||
return ShortLinesPosition.get(ShortLinesPosition.numberToEnumValue(newNum));
|
||||
public shiftWithWrap(dir: Hand, facing: Facing.Up | Facing.Down): { newPos: ShortLinesPosition, wrap?: -1 | 1 } {
|
||||
const shift = (dir === Hand.Left) === (facing === Facing.Down) ? -1 : +1;
|
||||
const newNum = ShortLinesPosition.enumValueToNumber(this.enumValue) + shift;
|
||||
const newPos = ShortLinesPosition.get(ShortLinesPosition.numberToEnumValue(newNum))
|
||||
if (newNum < 0) {
|
||||
return { newPos, wrap: -1 };
|
||||
} else if (newNum > 3) {
|
||||
return { newPos, wrap: +1 };
|
||||
} else {
|
||||
return { newPos };
|
||||
}
|
||||
}
|
||||
|
||||
public isMiddle() : boolean {
|
||||
|
@ -540,24 +552,27 @@ export const handsFourImproper: Map<DancerIdentity, SemanticPosition & { kind: P
|
|||
}],
|
||||
]);
|
||||
|
||||
export function handsInShortLine({ which, facing, wavy }: { which: ShortLinesPosition; facing: Facing.Up | Facing.Down; wavy: boolean; }): Map<Hand, HandConnection> {
|
||||
return which.isMiddle() ? new Map<Hand, HandConnection>([
|
||||
export function handsInLongLines(wavy: boolean) {
|
||||
return new Map<Hand, HandConnection>([
|
||||
[Hand.Left, { hand: wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }],
|
||||
[Hand.Right, { hand: wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }],
|
||||
]) : new Map<Hand, HandConnection>([
|
||||
which.isLeft() === (facing === Facing.Up)
|
||||
? [Hand.Left, { hand: wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }]
|
||||
: [Hand.Right, { hand: wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }]
|
||||
]);
|
||||
}
|
||||
|
||||
export function handsInShortLine({ which, facing, wavy }: { which: ShortLinesPosition; facing: Facing.Up | Facing.Down; wavy: boolean; }): Map<Hand, HandConnection> {
|
||||
return which.isMiddle()
|
||||
? handsInLongLines(wavy)
|
||||
: new Map<Hand, HandConnection>([
|
||||
which.isLeft() === (facing === Facing.Up)
|
||||
? [Hand.Left, { hand: wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }]
|
||||
: [Hand.Right, { hand: wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }]
|
||||
]);
|
||||
}
|
||||
export function handsInLine(args: { wavy: boolean, which: ShortLinesPosition | CirclePosition, facing?: Facing }) {
|
||||
if (args.which instanceof ShortLinesPosition && (args.facing === Facing.Up || args.facing === Facing.Down)) {
|
||||
return handsInShortLine({ wavy: args.wavy, which: args.which, facing: args.facing });
|
||||
} else {
|
||||
return new Map<Hand, HandConnection>([
|
||||
[Hand.Left, { hand: args.wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }],
|
||||
[Hand.Right, { hand: args.wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }],
|
||||
]);
|
||||
return handsInLongLines(args.wavy);
|
||||
}
|
||||
}
|
||||
export function handToDancerToSideInCircleFacingAcross(which: CirclePosition): Map<Hand, HandConnection> {
|
||||
|
|
|
@ -34,7 +34,7 @@ type chooser_slice_return = "straight" | "diagonal" | "none";
|
|||
type chooser_all_or_center_or_outsides = "all" | "center" | "outsides";
|
||||
type chooser_down_the_hall_ender = "turn-alone" | "turn-couple" | "circle" | "cozy" | "cloverleaf" | "thread-needle" | "right-high" | "sliding-doors" | "";
|
||||
type chooser_zig_zag_ender = "" | "ring" | "allemande";
|
||||
type chooser_hey_length = "full" | "half" | "less than half" | "between half and full";
|
||||
type chooser_hey_length = "full" | "half" | "less than half" | "between half and full" | { dancer: chooser_pairz, time: 1 | 2};
|
||||
type chooser_swing_prefix = "none" | "balance" | "meltdown";
|
||||
type chooser_dancers = "everyone" | "gentlespoon" | "gentlespoons" | "ladle" | "ladles" | "partners" | "neighbors" | "ones" | "twos" | "same roles" | "first corners" | "second corners" | "first gentlespoon" | "first ladle" | "second gentlespoon" | "second ladle" | "shadows";
|
||||
type chooser_pair = "gentlespoons" | "ladles" | "ones" | "twos" | "first corners" | "second corners";
|
||||
|
@ -410,7 +410,20 @@ export function nameLibFigureParameters(move: LibFigureMove): Move {
|
|||
.map((v, i, a) => a.indexOf(v) === i && a.lastIndexOf(v) === i ? v : v + (a.slice(0, i).filter(el => el === v).length + 1));
|
||||
const parameters = {};
|
||||
for (let i = 0; i < parameterNames.length; i++) {
|
||||
parameters[parameterNames[i]] = move.parameter_values[i];
|
||||
if (parameterNames[i] === "until") {
|
||||
// parse chooser_hey_length
|
||||
const hey_length: string = move.parameter_values[i];
|
||||
if (hey_length.includes('%%')) {
|
||||
parameters[parameterNames[i]] = {
|
||||
dancer: hey_length.slice(0, -3),
|
||||
time: parseInt(hey_length.at(-1)!),
|
||||
};
|
||||
} else {
|
||||
parameters[parameterNames[i]] = hey_length;
|
||||
}
|
||||
} else {
|
||||
parameters[parameterNames[i]] = move.parameter_values[i];
|
||||
}
|
||||
}
|
||||
const beats: number = parameters["beats"];
|
||||
parameters["beats"] = undefined;
|
||||
|
|
|
@ -395,6 +395,15 @@ export abstract class MoveInterpreter<N extends MoveName> {
|
|||
}
|
||||
|
||||
if (res.size === 0) throw error;
|
||||
else if (res.size > 1) {
|
||||
// TODO Try to reduce variants if possible.
|
||||
// XXX TODO Simple hack: perfer starting improper in a circle.
|
||||
if ([...res.values()].find(v => v.previousMoveVariant === "InitialCircle")) {
|
||||
for (const key of [...res.entries()].filter(([k, v]) => v.previousMoveVariant !== "InitialCircle").map(([k, v]) => k)) {
|
||||
res.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||
|
||||
type allemandeMoves = "allemande" | "allemande orbit" | "gyre";
|
||||
|
||||
|
@ -10,20 +10,18 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
// Need to store this locally so checking move.move restricts move.parameters.
|
||||
const move = this.move;
|
||||
|
||||
const allemandeCircling = move.move === "allemande orbit" ? move.parameters.circling1 : move.parameters.circling;
|
||||
const byHandOrShoulder = (move.move === "gyre" ? move.parameters.shoulder : move.parameters.hand) ? Hand.Right : Hand.Left;
|
||||
|
||||
// TODO Not sure if this is right.
|
||||
const swap = allemandeCircling % 360 === 180;
|
||||
const returnToStart = allemandeCircling % 360 === 0;
|
||||
const intoWave = !swap && !returnToStart && allemandeCircling % 90 == 0;
|
||||
const intoWavePositions = !intoWave ? 0 : (allemandeCircling % 360 === 90) === (byHandOrShoulder === Hand.Left) ? 1 : -1;
|
||||
if (!swap && !returnToStart && !intoWave) {
|
||||
// TODO Support allemande that's not a swap or no-op.
|
||||
throw "Unsupported allemande circle amount: " + allemandeCircling;
|
||||
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(move.parameters.who, ({ startPos, around, withId, withPos }) => {
|
||||
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())) {
|
||||
|
@ -32,16 +30,16 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
|
||||
let endPosition: SemanticPosition = {...startPos, dancerDistance: undefined, longLines: undefined, balance: undefined };
|
||||
let startingPos = {...startPos };
|
||||
if (swap) {
|
||||
if (this.moveInterpreter.swap) {
|
||||
// TODO This was more complicated. Is this wrong?
|
||||
endPosition = {...withPos, dancerDistance: undefined, longLines: undefined, balance: undefined };
|
||||
} else if (intoWave) {
|
||||
} 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) &&
|
||||
((byHandOrShoulder === Hand.Right)
|
||||
((this.moveInterpreter.byHandOrShoulder === Hand.Right)
|
||||
!== (startPos.facing === Facing.Up)
|
||||
!== startPos.which.isLeftOfSide())) {
|
||||
startWhich = startPos.which.swapOnSide()
|
||||
|
@ -53,14 +51,14 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
|
||||
const endWhich = CirclePosition.fromSides(startingPos.which.leftRightSide(),
|
||||
startWhich.isLeftOfSide()
|
||||
!== (byHandOrShoulder === Hand.Right)
|
||||
!== (intoWavePositions === 1)
|
||||
!== (this.moveInterpreter.byHandOrShoulder === Hand.Right)
|
||||
!== (this.moveInterpreter.intoWavePositions === 1)
|
||||
? CircleSide.Top
|
||||
: CircleSide.Bottom);
|
||||
endPosition = {
|
||||
kind: PositionKind.Circle,
|
||||
which: endWhich,
|
||||
facing: (startingPos.facing === Facing.Up) === (intoWavePositions === 1)
|
||||
facing: (startingPos.facing === Facing.Up) === (this.moveInterpreter.intoWavePositions === 1)
|
||||
? endWhich.facingAcross()
|
||||
: endWhich.facingOut(),
|
||||
setOffset: startingPos.setOffset,
|
||||
|
@ -72,7 +70,7 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
} else {
|
||||
if (around === "Center") {
|
||||
const startCenter = startPos.longLines === LongLines.Center;
|
||||
const endWhich = startPos.which.circleRight(intoWavePositions);
|
||||
const endWhich = startPos.which.circleRight(this.moveInterpreter.intoWavePositions);
|
||||
endPosition = {
|
||||
kind: PositionKind.Circle,
|
||||
which: endWhich,
|
||||
|
@ -82,11 +80,11 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
lineOffset: startPos.lineOffset,
|
||||
}
|
||||
} else {
|
||||
const endWhich = startPos.which.toShortLines(intoWavePositions === 1 ? Hand.Right : Hand.Left);
|
||||
const endWhich = startPos.which.toShortLines(this.moveInterpreter.intoWavePositions === 1 ? Hand.Right : Hand.Left);
|
||||
endPosition = {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: endWhich,
|
||||
facing: endWhich.isLeftOfSide() === (byHandOrShoulder === Hand.Left) ? Facing.Up : Facing.Down,
|
||||
facing: endWhich.isLeftOfSide() === (this.moveInterpreter.byHandOrShoulder === Hand.Left) ? Facing.Up : Facing.Down,
|
||||
setOffset: startPos.setOffset,
|
||||
lineOffset: startPos.lineOffset,
|
||||
}
|
||||
|
@ -96,30 +94,23 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
|
||||
return this.combine([
|
||||
{
|
||||
beats: move.beats,
|
||||
beats: this.move.beats,
|
||||
endPosition,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.RotateAround,
|
||||
minAmount: byHandOrShoulder === Hand.Right ? allemandeCircling : -allemandeCircling,
|
||||
minAmount: this.moveInterpreter.byHandOrShoulder === Hand.Right ? this.moveInterpreter.allemandeCircling : -this.moveInterpreter.allemandeCircling,
|
||||
around,
|
||||
byHand: move.move === "allemande" || move.move === "allemande orbit" ? byHandOrShoulder : undefined,
|
||||
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 === byHandOrShoulder))
|
||||
? new Map<Hand, HandConnection>([...startPos.hands.entries()].filter(([h, c]) => h === this.moveInterpreter.byHandOrShoulder))
|
||||
: undefined
|
||||
});
|
||||
}, move.move !== "allemande orbit" ? undefined : ({ id, startPos }) => {
|
||||
const orbitAmount = move.parameters.circling2;
|
||||
const swap = orbitAmount % 360 === 180;
|
||||
if (!swap && orbitAmount % 360 !== 0) {
|
||||
// TODO Support allemande that's not a swap or no-op.
|
||||
throw "Unsupported allemande orbit amount: " + orbitAmount;
|
||||
}
|
||||
|
||||
const startingPos: SemanticPosition = {
|
||||
...startPos,
|
||||
hands: undefined,
|
||||
|
@ -127,7 +118,7 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
dancerDistance: undefined,
|
||||
}
|
||||
let endPosition: SemanticPosition;
|
||||
if (swap) {
|
||||
if (this.moveInterpreter.orbitSwap) {
|
||||
if (startingPos.kind === PositionKind.Circle) {
|
||||
endPosition =
|
||||
{
|
||||
|
@ -154,7 +145,7 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
movementPattern: {
|
||||
kind: SemanticAnimationKind.RotateAround,
|
||||
// Orbit is opposite direction of allemande.
|
||||
minAmount: byHandOrShoulder === Hand.Right ? -orbitAmount : +orbitAmount,
|
||||
minAmount: this.moveInterpreter.byHandOrShoulder === Hand.Right ? -this.moveInterpreter.orbitCircling : +this.moveInterpreter.orbitCircling,
|
||||
around: "Center",
|
||||
byHand: undefined,
|
||||
close: false,
|
||||
|
@ -166,6 +157,48 @@ class AllemandeSingleVariant extends SingleVariantMoveInterpreter<Allemande, all
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,263 +1,242 @@
|
|||
import { DancerIdentity } from "../danceCommon.js";
|
||||
import { SemanticPosition, PositionKind, ShortLinesPosition, CirclePosition, CircleSide, Facing } from "../interpreterCommon.js";
|
||||
import { Move } from "../libfigureMapper.js";
|
||||
import { LowLevelMove, SemanticAnimationKind } from "../lowLevelMove.js";
|
||||
import { Hand } from "../rendererConstants.js";
|
||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||
import { dancerIsPair } from "../libfigure/util.js";
|
||||
|
||||
const moveName: Move["move"] = "hey";
|
||||
const moveName = "hey";
|
||||
|
||||
type HeyStep = {
|
||||
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut" | "Ricochet",
|
||||
endPosition: SemanticPosition,
|
||||
}
|
||||
|
||||
function fixupHeyOtherPath(withoutOtherPath: Map<DancerIdentity, (LowLevelMove & { heyStep?: HeyStep })[]>): Map<DancerIdentity, LowLevelMove[]> {
|
||||
const numSteps = withoutOtherPath.get(DancerIdentity.OnesLark)!.length;
|
||||
for (let i = 0; i < numSteps; i++) {
|
||||
for (const id of withoutOtherPath.keys()) {
|
||||
const lowLevelMove = withoutOtherPath.get(id)![i];
|
||||
if (lowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|
||||
|| !lowLevelMove.heyStep
|
||||
|| lowLevelMove.movementPattern.otherPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const heyStepKind = lowLevelMove.heyStep.kind;
|
||||
let foundPair = false;
|
||||
for (const otherId of withoutOtherPath.keys()) {
|
||||
const otherLowLevelMove = withoutOtherPath.get(otherId)![i];
|
||||
if (id === otherId
|
||||
|| otherLowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|
||||
|| !otherLowLevelMove.heyStep
|
||||
|| otherLowLevelMove.movementPattern.otherPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const otherHeyStepKind = otherLowLevelMove.heyStep.kind;
|
||||
|
||||
if (heyStepKind === "CenterPass" && otherHeyStepKind === "CenterPass"
|
||||
|| (lowLevelMove.startPosition.which.leftRightSide() === otherLowLevelMove.startPosition.which.leftRightSide()
|
||||
&& (heyStepKind === "EndsPassIn" && otherHeyStepKind === "EndsPassOut"
|
||||
|| heyStepKind === "EndsPassOut" && otherHeyStepKind === "EndsPassIn"))) {
|
||||
lowLevelMove.movementPattern.otherPath = {
|
||||
start: { ...otherLowLevelMove.startPosition, setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset },
|
||||
end: { ...otherLowLevelMove.endPosition, setOffset: lowLevelMove.endPosition.setOffset, lineOffset: lowLevelMove.endPosition.lineOffset },
|
||||
}
|
||||
otherLowLevelMove.movementPattern.otherPath = {
|
||||
start: { ...lowLevelMove.startPosition, setOffset: otherLowLevelMove.startPosition.setOffset, lineOffset: otherLowLevelMove.startPosition.lineOffset },
|
||||
end: { ...lowLevelMove.endPosition, setOffset: otherLowLevelMove.endPosition.setOffset, lineOffset: otherLowLevelMove.endPosition.lineOffset },
|
||||
}
|
||||
foundPair = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPair && heyStepKind === "EndsPassOut") {
|
||||
// Then other is standing still.
|
||||
const pos = {
|
||||
...([...withoutOtherPath.values()]
|
||||
.map(otherMoves => otherMoves[i])
|
||||
.filter(m => m.movementPattern.kind === SemanticAnimationKind.StandStill
|
||||
&& m.endPosition.which.leftRightSide() === lowLevelMove.endPosition.which.leftRightSide())
|
||||
[0].endPosition),
|
||||
setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset
|
||||
}
|
||||
lowLevelMove.movementPattern.otherPath = { start: pos, end: pos };
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of withoutOtherPath.keys()) {
|
||||
const lowLevelMove = withoutOtherPath.get(id)![i];
|
||||
if (lowLevelMove.movementPattern.kind === SemanticAnimationKind.PassBy
|
||||
&& !lowLevelMove.movementPattern.otherPath) {
|
||||
throw new Error("Failed to fill in otherPath for " + id + " on hey step " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Object was mutated.
|
||||
return withoutOtherPath;
|
||||
}
|
||||
|
||||
class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName> {
|
||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
||||
// Needed for inner functions... that probably should be methods.
|
||||
const move = this.move;
|
||||
heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
|
||||
return {
|
||||
beats: this.moveInterpreter.heyPartBeats,
|
||||
// TODO use circle positions on ends? ... unless hey ends in a box the gnat or similar...
|
||||
endPosition: heyStep.endPosition,
|
||||
movementPattern: heyStep.kind === "StandStill" ? {
|
||||
kind: SemanticAnimationKind.StandStill,
|
||||
} : heyStep.kind === "Loop" ? {
|
||||
// TODO Loop should probably be its own kind? Or RotateAround?
|
||||
kind: SemanticAnimationKind.Linear,
|
||||
minRotation: this.moveInterpreter.endsShoulder === Hand.Right ? +180 : -180,
|
||||
} : heyStep.kind === "Ricochet" ? {
|
||||
// TODO This is a hack.
|
||||
kind: SemanticAnimationKind.PassBy,
|
||||
around: heyStep.endPosition.which.leftRightSide(),
|
||||
withHands: false,
|
||||
otherPath: "Swap",
|
||||
facing: "Start",
|
||||
side: this.moveInterpreter.endsShoulder,
|
||||
} : {
|
||||
kind: SemanticAnimationKind.PassBy,
|
||||
around: heyStep.kind === "CenterPass" ? "Center" : heyStep.endPosition.which.leftRightSide(),
|
||||
withHands: false,
|
||||
side: heyStep.kind === "CenterPass" ? this.moveInterpreter.centerShoulder : this.moveInterpreter.endsShoulder,
|
||||
facing: "Start",
|
||||
otherPath: undefined!, // Placeholder, fixup later.
|
||||
},
|
||||
heyStep,
|
||||
};
|
||||
}
|
||||
|
||||
type HeyStep = {
|
||||
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut" | "Ricochet",
|
||||
endPosition: SemanticPosition,
|
||||
}
|
||||
continueHey(prevStep: HeyStep, stepsLeft: number,
|
||||
{ beenInCenter, endsInCircle, inCenterFirst }:
|
||||
{ beenInCenter: boolean, endsInCircle: boolean, inCenterFirst: boolean }): HeyStep {
|
||||
// TODO Not sure why type checker requires rechecking this here.
|
||||
if (this.move.move !== "hey") throw new Error("Unreachable.");
|
||||
|
||||
if (this.move.parameters.dir !== "across") {
|
||||
throw new Error("Unsupported hey direction: " + this.move.parameters.dir);
|
||||
}
|
||||
|
||||
let heyParts: number;
|
||||
switch (this.move.parameters.until) {
|
||||
case "half":
|
||||
heyParts = 4;
|
||||
break;
|
||||
case "full":
|
||||
heyParts = 8;
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unsupported hey 'until': " + this.move.parameters.until);
|
||||
}
|
||||
const heyPartBeats: number = this.move.beats / heyParts;
|
||||
// TODO is this right?
|
||||
const firstPassInCenter: boolean = dancerIsPair(this.move.parameters.who);
|
||||
const centerShoulder = firstPassInCenter === this.move.parameters.shoulder ? Hand.Right : Hand.Left;
|
||||
const endsShoulder = centerShoulder.opposite();
|
||||
|
||||
function fixupHeyOtherPath(withoutOtherPath: Map<DancerIdentity, (LowLevelMove & { heyStep?: HeyStep })[]>): Map<DancerIdentity, LowLevelMove[]> {
|
||||
const numSteps = withoutOtherPath.get(DancerIdentity.OnesLark)!.length;
|
||||
for (let i = 0; i < numSteps; i++) {
|
||||
for (const id of withoutOtherPath.keys()) {
|
||||
const lowLevelMove = withoutOtherPath.get(id)![i];
|
||||
if (lowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|
||||
|| !lowLevelMove.heyStep
|
||||
|| lowLevelMove.movementPattern.otherPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const heyStepKind = lowLevelMove.heyStep.kind;
|
||||
let foundPair = false;
|
||||
for (const otherId of withoutOtherPath.keys()) {
|
||||
const otherLowLevelMove = withoutOtherPath.get(otherId)![i];
|
||||
if (id === otherId
|
||||
|| otherLowLevelMove.movementPattern.kind !== SemanticAnimationKind.PassBy
|
||||
|| !otherLowLevelMove.heyStep
|
||||
|| otherLowLevelMove.movementPattern.otherPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const otherHeyStepKind = otherLowLevelMove.heyStep.kind;
|
||||
|
||||
if (heyStepKind === "CenterPass" && otherHeyStepKind === "CenterPass"
|
||||
|| (lowLevelMove.startPosition.which.leftRightSide() === otherLowLevelMove.startPosition.which.leftRightSide()
|
||||
&& (heyStepKind === "EndsPassIn" && otherHeyStepKind === "EndsPassOut"
|
||||
|| heyStepKind === "EndsPassOut" && otherHeyStepKind === "EndsPassIn"))) {
|
||||
lowLevelMove.movementPattern.otherPath = {
|
||||
start: { ...otherLowLevelMove.startPosition, setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset },
|
||||
end: { ...otherLowLevelMove.endPosition, setOffset: lowLevelMove.endPosition.setOffset, lineOffset: lowLevelMove.endPosition.lineOffset },
|
||||
}
|
||||
otherLowLevelMove.movementPattern.otherPath = {
|
||||
start: { ...lowLevelMove.startPosition, setOffset: otherLowLevelMove.startPosition.setOffset, lineOffset: otherLowLevelMove.startPosition.lineOffset },
|
||||
end: { ...lowLevelMove.endPosition, setOffset: otherLowLevelMove.endPosition.setOffset, lineOffset: otherLowLevelMove.endPosition.lineOffset },
|
||||
}
|
||||
foundPair = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPair && heyStepKind === "EndsPassOut") {
|
||||
// Then other is standing still.
|
||||
const pos = {
|
||||
...([...withoutOtherPath.values()]
|
||||
.map(otherMoves => otherMoves[i])
|
||||
.filter(m => m.movementPattern.kind === SemanticAnimationKind.StandStill
|
||||
&& m.endPosition.which.leftRightSide() === lowLevelMove.endPosition.which.leftRightSide())
|
||||
[0].endPosition),
|
||||
setOffset: lowLevelMove.startPosition.setOffset, lineOffset: lowLevelMove.startPosition.lineOffset
|
||||
}
|
||||
lowLevelMove.movementPattern.otherPath = { start: pos, end: pos };
|
||||
// Continuing hey so everyone is either passing (in center or on ends) or looping on ends.
|
||||
if (prevStep.endPosition.kind === PositionKind.Circle) {
|
||||
if (prevStep.endPosition.facing === prevStep.endPosition.which.facingAcross()) {
|
||||
if (stepsLeft === 0) {
|
||||
return {
|
||||
kind: "StandStill",
|
||||
endPosition: prevStep.endPosition,
|
||||
}
|
||||
}
|
||||
|
||||
for (const id of withoutOtherPath.keys()) {
|
||||
const lowLevelMove = withoutOtherPath.get(id)![i];
|
||||
if (lowLevelMove.movementPattern.kind === SemanticAnimationKind.PassBy
|
||||
&& !lowLevelMove.movementPattern.otherPath) {
|
||||
throw new Error("Failed to fill in otherPath for " + id + " on hey step " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Object was mutated.
|
||||
return withoutOtherPath;
|
||||
}
|
||||
|
||||
return fixupHeyOtherPath(this.handleMove(({ id, startPos }) => {
|
||||
const endsInCircle = startPos.kind === PositionKind.Circle;
|
||||
function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
|
||||
return {
|
||||
beats: heyPartBeats,
|
||||
// TODO use circle positions on ends? ... unless hey ends in a box the gnat or similar...
|
||||
endPosition: heyStep.endPosition,
|
||||
movementPattern: heyStep.kind === "StandStill" ? {
|
||||
kind: SemanticAnimationKind.StandStill,
|
||||
} : heyStep.kind === "Loop" ? {
|
||||
// TODO Loop should probably be its own kind? Or RotateAround?
|
||||
kind: SemanticAnimationKind.Linear,
|
||||
minRotation: endsShoulder === Hand.Right ? +180 : -180,
|
||||
} : heyStep.kind === "Ricochet" ? {
|
||||
// TODO This is a hack.
|
||||
kind: SemanticAnimationKind.PassBy,
|
||||
around: heyStep.endPosition.which.leftRightSide(),
|
||||
withHands: false,
|
||||
otherPath: "Swap",
|
||||
facing: "Start",
|
||||
side: endsShoulder,
|
||||
} : {
|
||||
kind: SemanticAnimationKind.PassBy,
|
||||
around: heyStep.kind === "CenterPass" ? "Center" : heyStep.endPosition.which.leftRightSide(),
|
||||
withHands: false,
|
||||
side: heyStep.kind === "CenterPass" ? centerShoulder : endsShoulder,
|
||||
facing: "Start",
|
||||
otherPath: undefined!, // Placeholder, fixup later.
|
||||
kind: "EndsPassIn",
|
||||
endPosition: {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.MiddleLeft : ShortLinesPosition.MiddleRight,
|
||||
facing: prevStep.endPosition.which.facingAcross(),
|
||||
setOffset: prevStep.endPosition.setOffset,
|
||||
lineOffset: prevStep.endPosition.lineOffset,
|
||||
},
|
||||
heyStep,
|
||||
};
|
||||
}
|
||||
}
|
||||
function continueHey(prevStep: HeyStep, stepsLeft: number, beenInCenter: boolean): HeyStep {
|
||||
// TODO Not sure why type checker requires rechecking this here.
|
||||
if (move.move !== "hey") throw new Error("Unreachable.");
|
||||
|
||||
// Continuing hey so everyone is either passing (in center or on ends) or looping on ends.
|
||||
if (prevStep.endPosition.kind === PositionKind.Circle) {
|
||||
if (prevStep.endPosition.facing === prevStep.endPosition.which.facingAcross()) {
|
||||
if (stepsLeft === 0) {
|
||||
return {
|
||||
kind: "StandStill",
|
||||
endPosition: prevStep.endPosition,
|
||||
}
|
||||
}
|
||||
return {
|
||||
kind: "EndsPassIn",
|
||||
endPosition: {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.MiddleLeft : ShortLinesPosition.MiddleRight,
|
||||
facing: prevStep.endPosition.which.facingAcross(),
|
||||
setOffset: prevStep.endPosition.setOffset,
|
||||
lineOffset: prevStep.endPosition.lineOffset,
|
||||
},
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (stepsLeft === 1 && !endsInCircle) {
|
||||
return {
|
||||
kind: "Loop",
|
||||
endPosition: {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.FarLeft : ShortLinesPosition.FarRight,
|
||||
facing: prevStep.endPosition.which.facingAcross(),
|
||||
setOffset: prevStep.endPosition.setOffset,
|
||||
lineOffset: prevStep.endPosition.lineOffset,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
kind: "Loop",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
which: prevStep.endPosition.which.swapUpAndDown(),
|
||||
facing: prevStep.endPosition.which.facingAcross()
|
||||
},
|
||||
}
|
||||
else {
|
||||
if (stepsLeft === 1 && !endsInCircle) {
|
||||
return {
|
||||
kind: "Loop",
|
||||
endPosition: {
|
||||
kind: PositionKind.ShortLines,
|
||||
which: prevStep.endPosition.which.isLeft() ? ShortLinesPosition.FarLeft : ShortLinesPosition.FarRight,
|
||||
facing: prevStep.endPosition.which.facingAcross(),
|
||||
setOffset: prevStep.endPosition.setOffset,
|
||||
lineOffset: prevStep.endPosition.lineOffset,
|
||||
},
|
||||
}
|
||||
}
|
||||
else if (prevStep.endPosition.kind === PositionKind.ShortLines) {
|
||||
const isFacingSide = prevStep.endPosition.facing === prevStep.endPosition.which.facingSide();
|
||||
const inMiddle = prevStep.endPosition.which.isMiddle();
|
||||
if (!inMiddle && !isFacingSide) {
|
||||
return {
|
||||
kind: "Loop",
|
||||
endPosition: { ...prevStep.endPosition, facing: prevStep.endPosition.which.facingSide() },
|
||||
}
|
||||
} else if (inMiddle && isFacingSide) {
|
||||
return {
|
||||
kind: "EndsPassOut",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
kind: PositionKind.Circle,
|
||||
which: prevStep.endPosition.which.isLeft()
|
||||
? (endsShoulder === Hand.Right ? CirclePosition.TopLeft : CirclePosition.BottomLeft)
|
||||
: (endsShoulder === Hand.Right ? CirclePosition.BottomRight : CirclePosition.TopRight),
|
||||
},
|
||||
}
|
||||
}
|
||||
else if (!isFacingSide) {
|
||||
const rico = inCenterFirst
|
||||
? beenInCenter
|
||||
? move.parameters.rico3
|
||||
: move.parameters.rico1
|
||||
: beenInCenter
|
||||
? move.parameters.rico4
|
||||
: move.parameters.rico2;
|
||||
return {
|
||||
kind: "Loop",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
which: prevStep.endPosition.which.swapUpAndDown(),
|
||||
facing: prevStep.endPosition.which.facingAcross()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prevStep.endPosition.kind === PositionKind.ShortLines) {
|
||||
const isFacingSide = prevStep.endPosition.facing === prevStep.endPosition.which.facingSide();
|
||||
const inMiddle = prevStep.endPosition.which.isMiddle();
|
||||
if (!inMiddle && !isFacingSide) {
|
||||
return {
|
||||
kind: "Loop",
|
||||
endPosition: { ...prevStep.endPosition, facing: prevStep.endPosition.which.facingSide() },
|
||||
}
|
||||
} else if (inMiddle && isFacingSide) {
|
||||
return {
|
||||
kind: "EndsPassOut",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
kind: PositionKind.Circle,
|
||||
which: prevStep.endPosition.which.isLeft()
|
||||
? (this.moveInterpreter.endsShoulder === Hand.Right ? CirclePosition.TopLeft : CirclePosition.BottomLeft)
|
||||
: (this.moveInterpreter.endsShoulder === Hand.Right ? CirclePosition.BottomRight : CirclePosition.TopRight),
|
||||
},
|
||||
}
|
||||
}
|
||||
else if (!isFacingSide) {
|
||||
const rico = inCenterFirst
|
||||
? beenInCenter
|
||||
? this.move.parameters.rico3
|
||||
: this.move.parameters.rico1
|
||||
: beenInCenter
|
||||
? this.move.parameters.rico4
|
||||
: this.move.parameters.rico2;
|
||||
|
||||
if (rico) {
|
||||
const onLeftSide = prevStep.endPosition.which.isLeft();
|
||||
return {
|
||||
kind: "Ricochet",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
kind: PositionKind.Circle,
|
||||
which: CirclePosition.fromSides(prevStep.endPosition.which.leftRightSide(),
|
||||
// TODO might be swapped
|
||||
(endsShoulder === Hand.Left) === onLeftSide ? CircleSide.Top : CircleSide.Bottom),
|
||||
facing: onLeftSide ? Facing.Left : Facing.Right,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
kind: "CenterPass",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
which: prevStep.endPosition.which.swapSides()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
kind: inMiddle ? "EndsPassOut" : "EndsPassIn",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
which: prevStep.endPosition.which.swapOnSide()
|
||||
},
|
||||
if (rico) {
|
||||
const onLeftSide = prevStep.endPosition.which.isLeft();
|
||||
return {
|
||||
kind: "Ricochet",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
kind: PositionKind.Circle,
|
||||
which: CirclePosition.fromSides(prevStep.endPosition.which.leftRightSide(),
|
||||
// TODO might be swapped
|
||||
(this.moveInterpreter.endsShoulder === Hand.Left) === onLeftSide ? CircleSide.Top : CircleSide.Bottom),
|
||||
facing: onLeftSide ? Facing.Left : Facing.Right,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unexpected PositionKind: " + (<any>prevStep.endPosition).kind);
|
||||
return {
|
||||
kind: "CenterPass",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
which: prevStep.endPosition.which.swapSides()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
kind: inMiddle ? "EndsPassOut" : "EndsPassIn",
|
||||
endPosition: {
|
||||
...prevStep.endPosition,
|
||||
which: prevStep.endPosition.which.swapOnSide()
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unexpected PositionKind: " + (<any>prevStep.endPosition).kind);
|
||||
}
|
||||
}
|
||||
|
||||
const inCenterFirst = firstPassInCenter && this.findPairOpposite(this.move.parameters.who, id) !== null
|
||||
|| this.move.parameters.who2 && this.findPairOpposite(this.move.parameters.who2, id) !== null;
|
||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
||||
return fixupHeyOtherPath(this.handleMove(({ id, startPos }) => {
|
||||
const endsInCircle = startPos.kind === PositionKind.Circle;
|
||||
|
||||
const inCenterFirst = this.moveInterpreter.firstPassInCenter && (this.findPairOpposite(this.move.parameters.who, id) !== null)
|
||||
|| !!this.move.parameters.who2 && (this.findPairOpposite(this.move.parameters.who2, id) !== null);
|
||||
|
||||
let firstHeyStep: HeyStep;
|
||||
let startingPos: SemanticPosition;
|
||||
if (firstPassInCenter) {
|
||||
if (this.moveInterpreter.firstPassInCenter) {
|
||||
if (startPos.kind !== PositionKind.Circle) {
|
||||
throw new Error("Hey starting in center not from circle is unsupported.");
|
||||
}
|
||||
|
@ -319,18 +298,60 @@ class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName
|
|||
|
||||
const heySteps: HeyStep[] = [firstHeyStep];
|
||||
let beenInCenter = firstHeyStep.kind === "CenterPass" || firstHeyStep.kind === "Ricochet";
|
||||
for (let i = 1; i < heyParts; i++) {
|
||||
const isLast = i === heyParts - 1;
|
||||
const nextHeyStep = continueHey(heySteps[i - 1], heyParts - i - 1, beenInCenter);
|
||||
for (let i = 1; i < this.moveInterpreter.heyParts; i++) {
|
||||
const nextHeyStep = this.continueHey(heySteps[i - 1], this.moveInterpreter.heyParts - i - 1, {beenInCenter, endsInCircle, inCenterFirst});
|
||||
beenInCenter ||= nextHeyStep.kind === "CenterPass" || nextHeyStep.kind === "Ricochet";
|
||||
heySteps.push(nextHeyStep);
|
||||
}
|
||||
return this.combine(heySteps.map(heyStepToPartialLowLevelMove), { ...startingPos, hands: undefined });
|
||||
return this.combine(heySteps.map(s => this.heyStepToPartialLowLevelMove(s)), { ...startingPos, hands: undefined });
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class Hey extends MoveInterpreter<typeof moveName> {
|
||||
public readonly heyParts: number;
|
||||
public readonly heyPartBeats: number;
|
||||
public readonly firstPassInCenter: boolean;
|
||||
public readonly centerShoulder: Hand;
|
||||
public readonly endsShoulder: Hand;
|
||||
|
||||
constructor(args: MoveInterpreterCtorArgs<typeof moveName>) {
|
||||
super(args);
|
||||
|
||||
if (this.move.parameters.dir !== "across") {
|
||||
throw new Error("Unsupported hey direction: " + this.move.parameters.dir);
|
||||
}
|
||||
|
||||
if (typeof this.move.parameters.until === "string") {
|
||||
switch (this.move.parameters.until) {
|
||||
case "half":
|
||||
this.heyParts = 4;
|
||||
break;
|
||||
case "full":
|
||||
this.heyParts = 8;
|
||||
break;
|
||||
// TODO Are these right? Can it sometimes be 1 or 3 instead of 2?
|
||||
case "less than half":
|
||||
this.heyParts = 2;
|
||||
break;
|
||||
case "between half and full":
|
||||
this.heyParts = 6;
|
||||
default:
|
||||
throw new Error("Unsupported hey 'until': " + this.move.parameters.until);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO Is this actually this simple?
|
||||
this.heyParts = this.move.parameters.until.time === 1 ? 2 : 6;
|
||||
//throw new Error("Unsupported hey 'until': " + this.move.parameters.until.dancer + " time " + this.move.parameters.until.time);
|
||||
}
|
||||
this.heyPartBeats = this.move.beats / this.heyParts;
|
||||
// TODO is this right?
|
||||
this.firstPassInCenter = dancerIsPair(this.move.parameters.who);
|
||||
this.centerShoulder = this.firstPassInCenter === this.move.parameters.shoulder ? Hand.Right : Hand.Left;
|
||||
this.endsShoulder = this.centerShoulder.opposite();
|
||||
}
|
||||
|
||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
||||
return new HeySingleVariant(this, startingPos);
|
||||
}
|
||||
|
|
|
@ -1,43 +1,71 @@
|
|||
import { HandConnection, HandTo, BalanceWeight } from "../interpreterCommon.js";
|
||||
import { Move } from "../libfigureMapper.js";
|
||||
import { HandConnection, HandTo, BalanceWeight, PositionKind, ShortLinesPosition, SemanticPosition } from "../interpreterCommon.js";
|
||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
||||
import { Hand } from "../rendererConstants.js";
|
||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, Variant, VariantCollection, moveInterpreters } from "./_moveInterpreter.js";
|
||||
|
||||
const moveName: Move["move"] = "pull by dancers";
|
||||
const moveName = "pull by dancers";
|
||||
|
||||
class PullByDancersSingleVariant extends SingleVariantMoveInterpreter<PullByDancers, typeof moveName> {
|
||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
||||
moveAsVariants(previousMoveVariant: string): VariantCollection {
|
||||
const res = new Map<string, Variant>();
|
||||
|
||||
for (const toShortLines of [true, false]) {
|
||||
try {
|
||||
res.set(toShortLines ? "ToShortLines" : "FullPullBy", {
|
||||
lowLevelMoves: this.moveAsLowLevelMovesTo(toShortLines),
|
||||
previousMoveVariant
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
moveAsLowLevelMovesTo(toShortLines: boolean): LowLevelMovesForAllDancers {
|
||||
// TODO Might make sense to think of pull by as not a full swap?
|
||||
// e.g., in Blue and Green Candles, it's treated as only getting to
|
||||
// ShortLinesPosition.Middle* before doing an allemande.
|
||||
return this.handlePairedMove(this.move.parameters.who, ({ startPos, around, withPos }) => {
|
||||
const hand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
||||
const balanceBeats = this.move.parameters.bal
|
||||
? this.move.beats > 4
|
||||
? this.move.beats - 4
|
||||
: 2
|
||||
: 0;
|
||||
const balancePartBeats = balanceBeats / 2;
|
||||
const pullBeats = this.move.beats - balanceBeats;
|
||||
if (toShortLines) {
|
||||
if (startPos.kind !== PositionKind.Circle) {
|
||||
throw new Error("ToShortLines variant of " + this.move.move + " only makes sense starting from a circle.");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Adjust facing?
|
||||
const startPosition = {
|
||||
...startPos,
|
||||
hands: new Map<Hand, HandConnection>([
|
||||
[
|
||||
hand,
|
||||
{ hand, to: around === "Center" ? HandTo.DiagonalAcrossCircle : HandTo.DancerForward }
|
||||
]])
|
||||
this.moveInterpreter.hand,
|
||||
{ hand: this.moveInterpreter.hand, to: around === "Center" ? HandTo.DiagonalAcrossCircle : HandTo.DancerForward }
|
||||
]]),
|
||||
facing: startPos.which.facingAcross(),
|
||||
};
|
||||
|
||||
const endPos: SemanticPosition = toShortLines
|
||||
? {
|
||||
...withPos,
|
||||
hands: undefined,
|
||||
facing: startPosition.facing,
|
||||
kind: PositionKind.ShortLines,
|
||||
which: ShortLinesPosition.fromSide(withPos.which.leftRightSide(), "Middle"),
|
||||
longLines: undefined, // Needed to satisfy type-checker.
|
||||
}
|
||||
: {
|
||||
...withPos,
|
||||
hands: undefined,
|
||||
facing: startPosition.facing,
|
||||
};
|
||||
const passBy: PartialLowLevelMove = {
|
||||
beats: pullBeats,
|
||||
endPosition: { ...withPos, facing: startPos.facing },
|
||||
beats: this.moveInterpreter.pullBeats,
|
||||
endPosition: endPos,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.PassBy,
|
||||
around,
|
||||
side: hand,
|
||||
side: this.moveInterpreter.hand,
|
||||
withHands: true,
|
||||
facing: "Start",
|
||||
otherPath: "Swap",
|
||||
|
@ -47,7 +75,7 @@ class PullByDancersSingleVariant extends SingleVariantMoveInterpreter<PullByDanc
|
|||
if (this.move.parameters.bal) {
|
||||
return this.combine([
|
||||
{
|
||||
beats: balancePartBeats,
|
||||
beats: this.moveInterpreter.balancePartBeats,
|
||||
endPosition: {
|
||||
...startPosition,
|
||||
balance: BalanceWeight.Forward,
|
||||
|
@ -57,7 +85,7 @@ class PullByDancersSingleVariant extends SingleVariantMoveInterpreter<PullByDanc
|
|||
}
|
||||
},
|
||||
{
|
||||
beats: balancePartBeats,
|
||||
beats: this.moveInterpreter.balancePartBeats,
|
||||
endPosition: {
|
||||
...startPosition,
|
||||
balance: BalanceWeight.Backward,
|
||||
|
@ -70,11 +98,45 @@ class PullByDancersSingleVariant extends SingleVariantMoveInterpreter<PullByDanc
|
|||
} else {
|
||||
return this.combine([passBy], startPosition);
|
||||
}
|
||||
}, !toShortLines ? undefined : ({startPos}) => {
|
||||
return this.combine([{
|
||||
beats: this.move.beats,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.Linear,
|
||||
},
|
||||
endPosition: {
|
||||
...startPos,
|
||||
kind: PositionKind.ShortLines,
|
||||
which: ShortLinesPosition.fromSide(startPos.which.leftRightSide(), "Far"),
|
||||
facing: startPos.which.facingAcross(),
|
||||
hands: undefined,
|
||||
longLines: undefined,
|
||||
}
|
||||
}], startPos);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class PullByDancers extends MoveInterpreter<typeof moveName> {
|
||||
public readonly hand: Hand;
|
||||
public readonly balancePartBeats: number;
|
||||
public readonly pullBeats: number;
|
||||
|
||||
constructor(args: MoveInterpreterCtorArgs<typeof moveName>) {
|
||||
super(args);
|
||||
|
||||
this.hand = this.move.parameters.hand ? Hand.Right : Hand.Left;
|
||||
const balanceBeats = this.move.parameters.bal
|
||||
? this.move.beats > 4
|
||||
? this.move.beats - 4
|
||||
: this.move.beats > 2
|
||||
? 2
|
||||
: this.move.beats / 2
|
||||
: 0;
|
||||
this.balancePartBeats = balanceBeats / 2;
|
||||
this.pullBeats = this.move.beats - balanceBeats;
|
||||
}
|
||||
|
||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
||||
return new PullByDancersSingleVariant(this, startingPos);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,14 @@
|
|||
import { PositionKind, SemanticPosition, Facing, BalanceWeight, handsInLine } from "../interpreterCommon.js";
|
||||
import { Move } from "../libfigureMapper.js";
|
||||
import { PositionKind, SemanticPosition, Facing, BalanceWeight, handsInLine, CircleSide, handsInLongLines } from "../interpreterCommon.js";
|
||||
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
||||
import { Hand } from "../rendererConstants.js";
|
||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, moveInterpreters } from "./_moveInterpreter.js";
|
||||
|
||||
const moveName: Move["move"] = "Rory O'More";
|
||||
const moveName = "Rory O'More";
|
||||
class RoryOMoreSingleVariant extends SingleVariantMoveInterpreter<RoryOMore, typeof moveName> {
|
||||
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
|
||||
if (this.move.parameters.who !== "everyone") {
|
||||
throw new Error(this.move.move + " that doesn't include everyone is unsupported.");
|
||||
}
|
||||
|
||||
// TODO Could be in long or short lines.
|
||||
const roryDir = this.move.parameters.slide ? Hand.Left : Hand.Right;
|
||||
const balBeats = this.move.parameters.bal ? this.move.beats / 2 : 0;
|
||||
const balPartBeats = balBeats / 2;
|
||||
const roryBeats = this.move.beats - balBeats;
|
||||
return this.handleMove(({ startPos }) => {
|
||||
const isShortLines: boolean = startPos.kind === PositionKind.ShortLines;
|
||||
|
||||
const startingPos: SemanticPosition = {
|
||||
...startPos,
|
||||
hands: handsInLine({ wavy: true, which: startPos.which, facing: startPos.facing })
|
||||
|
@ -27,38 +17,61 @@ class RoryOMoreSingleVariant extends SingleVariantMoveInterpreter<RoryOMore, typ
|
|||
let endPos: SemanticPosition;
|
||||
if (startPos.kind === PositionKind.ShortLines) {
|
||||
if (startPos.facing !== Facing.Up && startPos.facing !== Facing.Down) {
|
||||
throw new Error("To be in wavy lines, must be facing up or down, not " + startPos.facing);
|
||||
throw new Error("To be in short wavy lines, must be facing up or down, not " + startPos.facing);
|
||||
}
|
||||
const { newPos: endWhich, wrap } = startPos.which.shiftWithWrap(this.moveInterpreter.dir, startPos.facing);
|
||||
const endLineOffset = !wrap
|
||||
? startPos.lineOffset
|
||||
: (startPos.lineOffset ?? 0) + wrap;
|
||||
if (wrap) {
|
||||
startingPos.hands = handsInLongLines(true)
|
||||
}
|
||||
const endWhich = startPos.which.shift(roryDir, startPos.facing);
|
||||
endPos = {
|
||||
...startPos,
|
||||
which: endWhich,
|
||||
hands: handsInLine({ wavy: true, which: endWhich, facing: startPos.facing })
|
||||
hands: handsInLine({ wavy: true, which: endWhich, facing: startPos.facing }),
|
||||
lineOffset: endLineOffset,
|
||||
};
|
||||
} else {
|
||||
throw new Error(this.move.move + " is currently only supported in short lines.");
|
||||
if (startPos.facing !== Facing.Left && startPos.facing !== Facing.Right) {
|
||||
throw new Error("To be in long wavy lines, must be facing left or right, not " + startPos.facing);
|
||||
}
|
||||
const endWhich = startPos.which.swapUpAndDown();
|
||||
const sideTowards = (startPos.facing === Facing.Right)
|
||||
=== (this.moveInterpreter.dir === Hand.Left)
|
||||
? CircleSide.Bottom
|
||||
: CircleSide.Top;
|
||||
const endSetOffset = sideTowards === endWhich.topBottomSide()
|
||||
? startPos.setOffset
|
||||
: (startPos.setOffset ?? 0) + (sideTowards === CircleSide.Bottom ? +1 : -1);
|
||||
endPos = {
|
||||
...startPos,
|
||||
which: endWhich,
|
||||
hands: handsInLine({ wavy: true, which: endWhich, facing: startPos.facing }),
|
||||
setOffset: endSetOffset,
|
||||
};
|
||||
}
|
||||
|
||||
const maybeBalance: PartialLowLevelMove[] = (this.move.parameters.bal ? [
|
||||
{
|
||||
beats: balPartBeats,
|
||||
endPosition: { ...startingPos, balance: roryDir === Hand.Left ? BalanceWeight.Left : BalanceWeight.Right },
|
||||
beats: this.moveInterpreter.balPartBeats,
|
||||
endPosition: { ...startingPos, balance: this.moveInterpreter.dir === Hand.Left ? BalanceWeight.Left : BalanceWeight.Right },
|
||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
||||
},
|
||||
{
|
||||
beats: balPartBeats,
|
||||
endPosition: { ...startingPos, balance: roryDir === Hand.Left ? BalanceWeight.Right : BalanceWeight.Left },
|
||||
beats: this.moveInterpreter.balPartBeats,
|
||||
endPosition: { ...startingPos, balance: this.moveInterpreter.dir === Hand.Left ? BalanceWeight.Right : BalanceWeight.Left },
|
||||
movementPattern: { kind: SemanticAnimationKind.Linear },
|
||||
},
|
||||
] : []);
|
||||
|
||||
return this.combine([...maybeBalance,
|
||||
{
|
||||
beats: roryBeats,
|
||||
beats: this.moveInterpreter.roryBeats,
|
||||
endPosition: endPos,
|
||||
movementPattern: {
|
||||
kind: SemanticAnimationKind.Linear,
|
||||
minRotation: roryDir === Hand.Right ? +360 : -360,
|
||||
minRotation: this.moveInterpreter.dir === Hand.Right ? +360 : -360,
|
||||
handsDuring: "None",
|
||||
},
|
||||
}
|
||||
|
@ -68,6 +81,23 @@ class RoryOMoreSingleVariant extends SingleVariantMoveInterpreter<RoryOMore, typ
|
|||
}
|
||||
|
||||
class RoryOMore extends MoveInterpreter<typeof moveName> {
|
||||
public readonly dir: Hand;
|
||||
public readonly balPartBeats: number;
|
||||
public readonly roryBeats: number;
|
||||
|
||||
constructor(args: MoveInterpreterCtorArgs<typeof moveName>) {
|
||||
super(args);
|
||||
|
||||
if (this.move.parameters.who !== "everyone") {
|
||||
throw new Error(this.move.move + " that doesn't include everyone is unsupported.");
|
||||
}
|
||||
|
||||
this.dir = this.move.parameters.slide ? Hand.Left : Hand.Right;
|
||||
const balBeats = this.move.parameters.bal ? this.move.beats / 2 : 0;
|
||||
this.balPartBeats = balBeats / 2;
|
||||
this.roryBeats = this.move.beats - balBeats;
|
||||
}
|
||||
|
||||
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
||||
return new RoryOMoreSingleVariant(this, startingPos);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user