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

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