Compare commits

...

2 Commits

8 changed files with 566 additions and 351 deletions

View File

@ -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: pos }]
["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) }],
]);
}
}
export let mappedDance: Move[];

View File

@ -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 ShortLinesPosition.get(ShortLinesPosition.numberToEnumValue(newNum));
return newPos;
}
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,11 +552,17 @@ 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>([
]);
}
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 }]
@ -554,10 +572,7 @@ export function handsInLine(args: { wavy: boolean, which: ShortLinesPosition | C
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> {

View File

@ -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,8 +410,21 @@ 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++) {
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;
return {

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -1,45 +1,18 @@
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";
class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName> {
moveAsLowLevelMoves(): LowLevelMovesForAllDancers {
// Needed for inner functions... that probably should be methods.
const move = this.move;
type HeyStep = {
type HeyStep = {
kind: "StandStill" | "Loop" | "CenterPass" | "EndsPassIn" | "EndsPassOut" | "Ricochet",
endPosition: SemanticPosition,
}
}
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[]> {
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()) {
@ -105,13 +78,12 @@ class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName
// Object was mutated.
return withoutOtherPath;
}
}
return fixupHeyOtherPath(this.handleMove(({ id, startPos }) => {
const endsInCircle = startPos.kind === PositionKind.Circle;
function heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName> {
heyStepToPartialLowLevelMove(heyStep: HeyStep): PartialLowLevelMove & { heyStep: HeyStep } {
return {
beats: heyPartBeats,
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" ? {
@ -119,7 +91,7 @@ class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName
} : heyStep.kind === "Loop" ? {
// TODO Loop should probably be its own kind? Or RotateAround?
kind: SemanticAnimationKind.Linear,
minRotation: endsShoulder === Hand.Right ? +180 : -180,
minRotation: this.moveInterpreter.endsShoulder === Hand.Right ? +180 : -180,
} : heyStep.kind === "Ricochet" ? {
// TODO This is a hack.
kind: SemanticAnimationKind.PassBy,
@ -127,21 +99,24 @@ class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName
withHands: false,
otherPath: "Swap",
facing: "Start",
side: endsShoulder,
side: this.moveInterpreter.endsShoulder,
} : {
kind: SemanticAnimationKind.PassBy,
around: heyStep.kind === "CenterPass" ? "Center" : heyStep.endPosition.which.leftRightSide(),
withHands: false,
side: heyStep.kind === "CenterPass" ? centerShoulder : endsShoulder,
side: heyStep.kind === "CenterPass" ? this.moveInterpreter.centerShoulder : this.moveInterpreter.endsShoulder,
facing: "Start",
otherPath: undefined!, // Placeholder, fixup later.
},
heyStep,
};
}
function continueHey(prevStep: HeyStep, stepsLeft: number, beenInCenter: boolean): HeyStep {
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 (move.move !== "hey") throw new Error("Unreachable.");
if (this.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) {
@ -201,19 +176,19 @@ class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName
...prevStep.endPosition,
kind: PositionKind.Circle,
which: prevStep.endPosition.which.isLeft()
? (endsShoulder === Hand.Right ? CirclePosition.TopLeft : CirclePosition.BottomLeft)
: (endsShoulder === Hand.Right ? CirclePosition.BottomRight : CirclePosition.TopRight),
? (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
? move.parameters.rico3
: move.parameters.rico1
? this.move.parameters.rico3
: this.move.parameters.rico1
: beenInCenter
? move.parameters.rico4
: move.parameters.rico2;
? this.move.parameters.rico4
: this.move.parameters.rico2;
if (rico) {
const onLeftSide = prevStep.endPosition.which.isLeft();
@ -224,7 +199,7 @@ class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName
kind: PositionKind.Circle,
which: CirclePosition.fromSides(prevStep.endPosition.which.leftRightSide(),
// TODO might be swapped
(endsShoulder === Hand.Left) === onLeftSide ? CircleSide.Top : CircleSide.Bottom),
(this.moveInterpreter.endsShoulder === Hand.Left) === onLeftSide ? CircleSide.Top : CircleSide.Bottom),
facing: onLeftSide ? Facing.Left : Facing.Right,
}
}
@ -252,12 +227,16 @@ class HeySingleVariant extends SingleVariantMoveInterpreter<Hey, typeof moveName
}
}
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);
}

View File

@ -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);
}

View File

@ -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);
}