import { DancerIdentity, Rotation } from "./danceCommon.js"; import { Hand } from "./rendererConstants.js"; export enum PositionKind { Circle = "Circle", ShortLines = "ShortLines", }; // Which of the four corners of a circle. // Note that "Left" is the left side of the set, not left from the dancer's PoV, // so, in improper, the Robin starts in the BottomLeft. enum CirclePositionEnum { TopLeft = "TopLeft", BottomLeft = "BottomLeft", BottomRight = "BottomRight", TopRight = "TopRight", } export enum CircleSide { Top = "Top", Bottom = "Bottom", Left = "Left", Right = "Right", } export type CircleSideOrCenter = CircleSide | "Center"; export class CirclePosition { public static readonly TopLeft = new CirclePosition(CirclePositionEnum.TopLeft); public static readonly BottomLeft = new CirclePosition(CirclePositionEnum.BottomLeft); public static readonly BottomRight = new CirclePosition(CirclePositionEnum.BottomRight); public static readonly TopRight = new CirclePosition(CirclePositionEnum.TopRight); private readonly enumValue: CirclePositionEnum; private constructor(enumValue: CirclePositionEnum) { this.enumValue = enumValue; } public static fromSides(leftRightSide: CircleSide.Left | CircleSide.Right, topBottomSide: CircleSide.Bottom | CircleSide.Top) { return leftRightSide === CircleSide.Left ? topBottomSide === CircleSide.Top ? CirclePosition.TopLeft : CirclePosition.BottomLeft : topBottomSide === CircleSide.Top ? CirclePosition.TopRight : CirclePosition.BottomRight; } private static enumValueToNumber(enumValue: CirclePositionEnum) : number { switch (enumValue) { case CirclePositionEnum.TopLeft: return 0; case CirclePositionEnum.BottomLeft: return 1; case CirclePositionEnum.BottomRight: return 2; case CirclePositionEnum.TopRight: return 3; } } private static numberToEnumValue(num: number) : CirclePositionEnum { if (num < 0 || num > 3) { num %= 4; if (num < 0) num += 4; } return [ CirclePositionEnum.TopLeft, CirclePositionEnum.BottomLeft, CirclePositionEnum.BottomRight, CirclePositionEnum.TopRight, ][num]; } private static get(enumValue: CirclePositionEnum) : CirclePosition { return [ CirclePosition.TopLeft, CirclePosition.BottomLeft, CirclePosition.BottomRight, CirclePosition.TopRight, ][this.enumValueToNumber(enumValue)]; } public circleRight(places?: number) : CirclePosition { return CirclePosition.get(CirclePosition.numberToEnumValue(CirclePosition.enumValueToNumber(this.enumValue) - (places ?? 1))); } public circleLeft(places?: number) : CirclePosition { return CirclePosition.get(CirclePosition.numberToEnumValue(CirclePosition.enumValueToNumber(this.enumValue) + (places ?? 1))); } public swapAcross() : CirclePosition { return [ CirclePosition.TopRight, CirclePosition.BottomRight, CirclePosition.BottomLeft, CirclePosition.TopLeft, ][CirclePosition.enumValueToNumber(this.enumValue)]; } public swapUpAndDown() : CirclePosition { return [ CirclePosition.BottomLeft, CirclePosition.TopLeft, CirclePosition.TopRight, CirclePosition.BottomRight, ][CirclePosition.enumValueToNumber(this.enumValue)]; } public toShortLines(slideTo: Hand) : ShortLinesPosition { return slideTo === Hand.Left ? [ ShortLinesPosition.FarLeft, ShortLinesPosition.MiddleLeft, ShortLinesPosition.FarRight, ShortLinesPosition.MiddleRight, ][CirclePosition.enumValueToNumber(this.enumValue)] : [ ShortLinesPosition.MiddleLeft, ShortLinesPosition.FarLeft, ShortLinesPosition.MiddleRight, ShortLinesPosition.FarRight, ][CirclePosition.enumValueToNumber(this.enumValue)]; } public unfoldToShortLines(center: CircleSide.Bottom | CircleSide.Top) : ShortLinesPosition { return center === CircleSide.Bottom ? [ ShortLinesPosition.FarLeft, ShortLinesPosition.MiddleLeft, ShortLinesPosition.MiddleRight, ShortLinesPosition.FarRight, ][CirclePosition.enumValueToNumber(this.enumValue)] : [ ShortLinesPosition.MiddleLeft, ShortLinesPosition.FarLeft, ShortLinesPosition.FarRight, ShortLinesPosition.MiddleRight, ][CirclePosition.enumValueToNumber(this.enumValue)]; } public swapDiagonal() : CirclePosition { return this.swapAcross().swapUpAndDown(); } public facingCenterRotation() : Rotation { return (45 + 90 * CirclePosition.enumValueToNumber(this.enumValue)) % 360; } public topBottomSide() : CircleSide.Top | CircleSide.Bottom { return this.enumValue === CirclePositionEnum.TopLeft || this.enumValue === CirclePositionEnum.TopRight ? CircleSide.Top : CircleSide.Bottom; } public leftRightSide() : CircleSide.Left | CircleSide.Right { return this.enumValue === CirclePositionEnum.TopLeft || this.enumValue === CirclePositionEnum.BottomLeft ? CircleSide.Left : CircleSide.Right; } public isTop() : boolean { return this.topBottomSide() === CircleSide.Top; } public isLeft() : boolean { return this.leftRightSide() === CircleSide.Left; } public isOnLeftLookingAcross() : boolean { return this.enumValue === CirclePositionEnum.TopRight || this.enumValue === CirclePositionEnum.BottomLeft; } public isOnLeftLookingUpAndDown() : boolean { return this.enumValue === CirclePositionEnum.TopLeft || this.enumValue === CirclePositionEnum.BottomRight; } public facingAcross() : Facing.Left | Facing.Right { return this.isLeft() ? Facing.Right : Facing.Left; } public facingOut() : Facing.Left | Facing.Right { return this.isLeft() ? Facing.Left : Facing.Right; } public facingUpOrDown() : Facing.Up | Facing.Down { return this.isTop() ? Facing.Down : Facing.Up; } public toString() : string { return this.enumValue.toString(); } } enum ShortLinesPositionEnum { FarLeft = "FarLeft", MiddleLeft = "MiddleLeft", MiddleRight = "MiddleRight", FarRight = "FarRight", } export class ShortLinesPosition { public static readonly FarLeft = new ShortLinesPosition(ShortLinesPositionEnum.FarLeft); public static readonly MiddleLeft = new ShortLinesPosition(ShortLinesPositionEnum.MiddleLeft); public static readonly MiddleRight = new ShortLinesPosition(ShortLinesPositionEnum.MiddleRight); public static readonly FarRight = new ShortLinesPosition(ShortLinesPositionEnum.FarRight); private readonly enumValue: ShortLinesPositionEnum; private constructor(enumValue: ShortLinesPositionEnum) { this.enumValue = enumValue; } private static enumValueToNumber(enumValue: ShortLinesPositionEnum) : number { switch (enumValue) { case ShortLinesPositionEnum.FarLeft: return 0; case ShortLinesPositionEnum.MiddleLeft: return 1; case ShortLinesPositionEnum.MiddleRight: return 2; case ShortLinesPositionEnum.FarRight: return 3; } } private static numberToEnumValue(num: number) : ShortLinesPositionEnum { if (num < 0 || num > 3) { num %= 4; if (num < 0) num += 4; } return [ ShortLinesPositionEnum.FarLeft, ShortLinesPositionEnum.MiddleLeft, ShortLinesPositionEnum.MiddleRight, ShortLinesPositionEnum.FarRight, ][num]; } private static get(enumValue: ShortLinesPositionEnum) : ShortLinesPosition { return [ ShortLinesPosition.FarLeft, ShortLinesPosition.MiddleLeft, ShortLinesPosition.MiddleRight, ShortLinesPosition.FarRight, ][this.enumValueToNumber(enumValue)]; } public swapOnSide() : ShortLinesPosition { return [ ShortLinesPosition.MiddleLeft, ShortLinesPosition.FarLeft, ShortLinesPosition.FarRight, ShortLinesPosition.MiddleRight, ][ShortLinesPosition.enumValueToNumber(this.enumValue)]; } public swapSides() : ShortLinesPosition { return [ ShortLinesPosition.FarRight, ShortLinesPosition.MiddleRight, ShortLinesPosition.MiddleLeft, ShortLinesPosition.FarLeft, ][ShortLinesPosition.enumValueToNumber(this.enumValue)]; } 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) { throw new Error("Invalid shift: " + this + " facing " + facing + " to " + dir + "."); } return ShortLinesPosition.get(ShortLinesPosition.numberToEnumValue(newNum)); } public isMiddle() : boolean { return this.enumValue === ShortLinesPositionEnum.MiddleRight || this.enumValue === ShortLinesPositionEnum.MiddleLeft; } public leftRightSide() : CircleSide.Left | CircleSide.Right { return this.enumValue === ShortLinesPositionEnum.FarLeft || this.enumValue === ShortLinesPositionEnum.MiddleLeft ? CircleSide.Left : CircleSide.Right; } public isLeft() : boolean { return this.leftRightSide() === CircleSide.Left; } // Of the two positions on the same leftRightSide() is this the one further to the left? public isLeftOfSide() : boolean { return this.enumValue === ShortLinesPositionEnum.FarLeft || this.enumValue === ShortLinesPositionEnum.MiddleRight; } public facingSide() : Facing.Left | Facing.Right { return this.isLeft() === this.isMiddle() ? Facing.Left : Facing.Right; } public toString() : string { return this.enumValue.toString(); } } export enum Facing { CenterOfCircle = "CenterOfCircle", // for star left/right LeftInCircle = "LeftInCircle", RightInCircle = "RightInCircle", Up = "Up", Down = "Down", Left = "Left", Right = "Right", } export function oppositeFacing(facing: Facing.Up | Facing.Down | Facing.Left | Facing.Right | Facing.LeftInCircle | Facing.RightInCircle) { switch (facing) { case Facing.LeftInCircle: return Facing.RightInCircle; case Facing.RightInCircle: return Facing.LeftInCircle; case Facing.Up: return Facing.Down; case Facing.Down: return Facing.Up; case Facing.Left: return Facing.Right; case Facing.Right: return Facing.Left; } } export enum StarGrip { HandsAcross = "HandsAcross", WristGrip = "WristGrip", } export enum HandTo { LeftInCircle = "LeftInCircle", RightInCircle = "RightInCircle", AcrossCircle = "AcrossCircle", DiagonalAcrossCircle = "DiagonalAcrossCircle", LeftDiagonalAcrossCircle = "LeftDiagonalAcrossCircle", RightDiagonalAcrossCircle = "RightDiagonalAcrossCircle", DancerForward = "DancerForward", DancerLeft = "DancerLeft", DancerRight = "DancerRight", } export interface HandConnection { to: HandTo, hand: Hand, } export enum BalanceWeight { Forward = "Forward", Backward = "Backward", Left = "Left", Right = "Right", } export enum DancerDistance { Normal = "Normal", Compact = "Compact", // allemande, etc. // Swings asymmetrical, but some dances may have the Lark do a swing as a Robin or vice versa, // especially any dance with Larks Swing or Robins Swing. SwingLark = "SwingLark", SwingRobin = "SwingRobin", } export enum LongLines { // Walked forward into center. Forward = "Forward", // Only a little offset (has walked almost all the way from the other side after a give and take). Near = "Near", // Actually in center. May be slightly offset for wavy lines. Center = "Center", } export type SemanticPosition = { kind: PositionKind.Circle, setOffset?: number, lineOffset?: number, which: CirclePosition, facing: Facing, hands?: Map, balance?: BalanceWeight, dancerDistance?: DancerDistance, longLines?: LongLines, } | { kind: PositionKind.ShortLines, setOffset?: number, lineOffset?: number, which: ShortLinesPosition, facing: Facing, hands?: Map, balance?: BalanceWeight, dancerDistance?: DancerDistance, longLines?: undefined, }; export const handsInCircle = new Map([ [Hand.Left, { to: HandTo.LeftInCircle, hand: Hand.Right, }], [Hand.Right, { to: HandTo.RightInCircle, hand: Hand.Left, }], ]); export const handsFourImproper: Map = new Map([ [DancerIdentity.OnesLark, { kind: PositionKind.Circle, which: CirclePosition.TopLeft, facing: Facing.CenterOfCircle, hands: handsInCircle, }], [DancerIdentity.OnesRobin, { kind: PositionKind.Circle, which: CirclePosition.TopRight, facing: Facing.CenterOfCircle, hands: handsInCircle, }], [DancerIdentity.TwosLark, { kind: PositionKind.Circle, which: CirclePosition.BottomRight, facing: Facing.CenterOfCircle, hands: handsInCircle, }], [DancerIdentity.TwosRobin, { kind: PositionKind.Circle, which: CirclePosition.BottomLeft, facing: Facing.CenterOfCircle, hands: handsInCircle, }], ]); export function handsInShortLine({ which, facing, wavy }: { which: ShortLinesPosition; facing: Facing.Up | Facing.Down; wavy: boolean; }): Map { return which.isMiddle() ? new Map([ [Hand.Left, { hand: wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }], [Hand.Right, { hand: wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }], ]) : new Map([ 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.Left, { hand: args.wavy ? Hand.Right : Hand.Left, to: HandTo.DancerLeft }], [Hand.Right, { hand: args.wavy ? Hand.Left : Hand.Right, to: HandTo.DancerRight }], ]); } } export function handToDancerToSideInCircleFacingAcross(which: CirclePosition): Map { return new Map([ which.isOnLeftLookingAcross() ? [Hand.Right, { hand: Hand.Left, to: HandTo.DancerRight }] : [Hand.Left, { hand: Hand.Right, to: HandTo.DancerLeft }] ]); } export function handToDancerToSideInCircleFacingUpOrDown(which: CirclePosition): Map { return new Map([ which.isOnLeftLookingUpAndDown() ? [Hand.Right, { hand: Hand.Left, to: HandTo.DancerRight }] : [Hand.Left, { hand: Hand.Right, to: HandTo.DancerLeft }] ]); }