241 lines
9.2 KiB
TypeScript
241 lines
9.2 KiB
TypeScript
import { CoupleRole, DanceRole } from "../danceCommon.js";
|
|
import { LongLines, CircleSide, SemanticPosition, CirclePosition, Facing, PositionKind, HandConnection, HandTo, ShortLinesPosition, handsInLine, BalanceWeight, DancerDistance, isFacingUpOrDown, isLeftRightCircleSide } from "../interpreterCommon.js";
|
|
import { SemanticAnimationKind } from "../lowLevelMove.js";
|
|
import { Hand } from "../rendererConstants.js";
|
|
import { ISingleVariantMoveInterpreter, LowLevelMovesForAllDancers, MoveInterpreter, MoveInterpreterCtorArgs, PartialLowLevelMove, SemanticPositionsForAllDancers, SingleVariantMoveInterpreter, Variant, VariantCollection, moveInterpreters } from "./_moveInterpreter.js";
|
|
|
|
const moveName = "swing";
|
|
|
|
/* Possible swings:
|
|
* - default: start on left/right side, end facing across
|
|
* - give and take: start in one corner, end facing across
|
|
* - contra corners: just one pair, start on top/bottom side, end facing in to new circle
|
|
* - to short lines: start on left/right side, end facing up/down in short lines
|
|
* - same role: larks/robins go into center, end facing out
|
|
* - join short lines: just one pair in center, joins hands in short lines after
|
|
* - ... ?
|
|
*/
|
|
interface SwingEnd {
|
|
facing: "Across" | Facing.Up | Facing.Down,
|
|
close: boolean,
|
|
}
|
|
function swingEndString(swingEnd: SwingEnd): string {
|
|
return swingEnd.facing + (swingEnd.close ? "Close" : "");
|
|
}
|
|
const swingEndValues: SwingEnd[] = [
|
|
{ facing: "Across", close: false},
|
|
{ facing: "Across", close: true},
|
|
{ facing: Facing.Up, close: false},
|
|
{ facing: Facing.Up, close: true},
|
|
{ facing: Facing.Down, close: false},
|
|
{ facing: Facing.Down, close: true},
|
|
];
|
|
|
|
class SwingSingleVariant extends SingleVariantMoveInterpreter<Swing, typeof moveName> {
|
|
moveAsVariants(previousMoveVariant: string): VariantCollection {
|
|
const res = new Map<string, Variant>();
|
|
|
|
for (const swingEnd of swingEndValues) {
|
|
try {
|
|
res.set(swingEndString(swingEnd), {
|
|
lowLevelMoves: this.moveAsLowLevelMovesFacing(swingEnd),
|
|
previousMoveVariant
|
|
});
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
moveAsLowLevelMovesFacing(swingEnd: SwingEnd): LowLevelMovesForAllDancers {
|
|
return this.handlePairedMove(this.move.parameters.who, ({ id, startPos, around, withId, withPos }) => {
|
|
if (startPos.longLines && startPos.longLines !== LongLines.Near) {
|
|
throw new Error(this.move.move + " shouldn't start with long lines " + startPos.longLines);
|
|
}
|
|
|
|
const afterTake = startPos.longLines === LongLines.Near || withPos.longLines === LongLines.Near;
|
|
|
|
if (around === "Center" && swingEnd.facing === "Across") {
|
|
throw new Error("Cannot end a swing around the center facing across.");
|
|
}
|
|
|
|
const toShortLines = around === "Center" || (isLeftRightCircleSide(around) && isFacingUpOrDown(swingEnd.facing));
|
|
|
|
const startWhich = startPos.which;
|
|
const startPosition: SemanticPosition = {
|
|
...startPos,
|
|
facing: isLeftRightCircleSide(around)
|
|
? (startWhich instanceof CirclePosition
|
|
? afterTake
|
|
? startPos.longLines === LongLines.Near ? startWhich.facingOut() : startWhich.facingAcross()
|
|
: startWhich.facingUpOrDown()
|
|
: startWhich.facingSide())
|
|
: startWhich.facingAcross(),
|
|
};
|
|
|
|
const swingRole = id.danceRole != withId.setIdentity.danceRole
|
|
? id.danceRole
|
|
// Make some arbitrary choice for same-role swings
|
|
: id.coupleRole !== withId.setIdentity.coupleRole
|
|
? (id.coupleRole === CoupleRole.Ones ? DanceRole.Lark : DanceRole.Robin)
|
|
: withId.relativeSet !== 0
|
|
? (withId.relativeSet > 0 ? DanceRole.Lark : DanceRole.Robin)
|
|
: withId.relativeLine !== 0
|
|
? (withId.relativeLine > 0 ? DanceRole.Lark : DanceRole.Robin)
|
|
: /* should be unreachable as this means withId is equal to id */ DanceRole.Lark;
|
|
|
|
const endFacing = swingEnd.facing === "Across" ? startWhich.facingAcross() : swingEnd.facing;
|
|
const endWhich = toShortLines && /*always true, just for type-checker*/isFacingUpOrDown(swingEnd.facing)
|
|
? ShortLinesPosition.fromSwing(around, swingRole, swingEnd.facing)
|
|
: CirclePosition.fromSwing(/*always true, just for type-checker*/<CircleSide>around, swingRole, swingEnd.facing)
|
|
|
|
const endHands = toShortLines
|
|
? handsInLine({ wavy: false, which: endWhich, facing: endFacing })
|
|
: new Map<Hand, HandConnection>([swingRole === DanceRole.Lark
|
|
? [Hand.Right, { to: HandTo.DancerRight, hand: Hand.Left }]
|
|
: [Hand.Left, { to: HandTo.DancerLeft, hand: Hand.Right }]]);
|
|
|
|
const partialEndPosition = {
|
|
...startPos,
|
|
facing: endFacing,
|
|
balance: undefined,
|
|
dancerDistance: swingEnd.close ? DancerDistance.Compact : undefined,
|
|
longLines: undefined,
|
|
hands: endHands,
|
|
}
|
|
let endPosition: SemanticPosition = toShortLines ? {
|
|
...partialEndPosition,
|
|
kind: PositionKind.ShortLines,
|
|
which: <ShortLinesPosition>endWhich,
|
|
} : {
|
|
...partialEndPosition,
|
|
kind: PositionKind.Circle,
|
|
which: <CirclePosition>endWhich,
|
|
}
|
|
|
|
const swing: PartialLowLevelMove = {
|
|
beats: this.moveInterpreter.swingBeats,
|
|
endPosition: endPosition,
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.Swing,
|
|
minAmount: 360,
|
|
around,
|
|
endFacing,
|
|
swingRole,
|
|
afterTake,
|
|
},
|
|
};
|
|
|
|
switch (this.move.parameters.prefix) {
|
|
case "none":
|
|
return this.combine([swing,], startPosition);
|
|
case "balance":
|
|
const startWithBalHands = {
|
|
...startPosition,
|
|
hands: new Map<Hand, HandConnection>([
|
|
[Hand.Left, { to: HandTo.DancerForward, hand: Hand.Right }],
|
|
[Hand.Right, { to: HandTo.DancerForward, hand: Hand.Left }],
|
|
]),
|
|
};
|
|
const balForwardPos = startWithBalHands.kind === PositionKind.Circle
|
|
? {
|
|
...startWithBalHands,
|
|
balance: BalanceWeight.Forward,
|
|
dancerDistance: DancerDistance.Compact,
|
|
}
|
|
: {
|
|
...startWithBalHands,
|
|
balance: BalanceWeight.Forward,
|
|
};
|
|
return this.combine([
|
|
{
|
|
beats: this.moveInterpreter.balancePartBeats,
|
|
startPosition: startWithBalHands,
|
|
endPosition: balForwardPos,
|
|
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
|
},
|
|
prevEnd => ({
|
|
beats: this.moveInterpreter.balancePartBeats,
|
|
endPosition: {
|
|
...prevEnd,
|
|
balance: BalanceWeight.Backward,
|
|
},
|
|
movementPattern: { kind: SemanticAnimationKind.Linear, },
|
|
}),
|
|
swing,
|
|
], startPosition);
|
|
case "meltdown":
|
|
return this.combine([
|
|
prevEnd => ({
|
|
beats: this.moveInterpreter.meltdownBeats,
|
|
endPosition: { ...prevEnd, dancerDistance: DancerDistance.Compact },
|
|
movementPattern: {
|
|
kind: SemanticAnimationKind.RotateAround,
|
|
minAmount: 360,
|
|
around,
|
|
byHand: undefined,
|
|
close: true,
|
|
},
|
|
}),
|
|
swing,
|
|
], startPosition);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
class Swing extends MoveInterpreter<typeof moveName> {
|
|
public readonly swingBeats: number;
|
|
public readonly balancePartBeats: number;
|
|
public readonly meltdownBeats: number;
|
|
|
|
constructor(args: MoveInterpreterCtorArgs<typeof moveName>) {
|
|
super(args);
|
|
|
|
switch(this.move.parameters.prefix) {
|
|
case "none":
|
|
this.balancePartBeats = 0;
|
|
this.meltdownBeats = 0;
|
|
this.swingBeats = this.move.beats;
|
|
break;
|
|
case "balance":
|
|
this.meltdownBeats = 0;
|
|
this.balancePartBeats = this.move.beats > 8
|
|
? (this.move.beats - 8) / 2
|
|
: this.move.beats > 4
|
|
? 2
|
|
: this.move.beats / 4;
|
|
this.swingBeats = this.move.beats - this.balancePartBeats * 2;
|
|
break;
|
|
case "meltdown":
|
|
this.balancePartBeats = 0;
|
|
this.meltdownBeats = this.move.beats >= 8 ? 4 : this.move.beats / 2;
|
|
this.swingBeats = this.move.beats - this.meltdownBeats;
|
|
break;
|
|
default:
|
|
throw new Error ("Unknown swing prefix: " + this.move.parameters.prefix);
|
|
}
|
|
this.swingBeats = this.move.parameters.prefix === "none" ? this.move.beats
|
|
: this.move.parameters.prefix === "balance"
|
|
? this.move.beats > 8 ? 8 : this.move.beats - 4
|
|
: this.move.parameters.prefix === "meltdown"
|
|
? this.move.beats - 4
|
|
: (() => { throw "Unknown swing prefix: " + this.move.parameters.prefix })();
|
|
}
|
|
|
|
override allowStartingClose(): boolean {
|
|
return true;
|
|
}
|
|
|
|
override allowStartingLongLines(): boolean {
|
|
// Long lines is used for LongLines.Near after give and take.
|
|
return true;
|
|
}
|
|
|
|
buildSingleVariantMoveInterpreter(startingPos: SemanticPositionsForAllDancers): ISingleVariantMoveInterpreter {
|
|
return new SwingSingleVariant(this, startingPos);
|
|
}
|
|
}
|
|
|
|
moveInterpreters.set(moveName, Swing); |