forked from perelman/contra-renderer
681 lines
23 KiB
TypeScript
681 lines
23 KiB
TypeScript
import { DanceRole, 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 function isLeftRightCircleSide(side: CircleSideOrCenter): side is CircleSide.Left | CircleSide.Right {
|
|
return side === CircleSide.Left || side === CircleSide.Right;
|
|
}
|
|
export function isTopBottomCircleSide(side: CircleSideOrCenter): side is CircleSide.Top | CircleSide.Bottom {
|
|
return side === CircleSide.Top || side === CircleSide.Bottom;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public static fromSwing(side: CircleSide, swingRole: DanceRole, facing: "In" | "Out" | "Across" | Facing.Up | Facing.Down) {
|
|
if (isLeftRightCircleSide(side)) {
|
|
if (facing === Facing.Up || facing === Facing.Down) {
|
|
throw new Error("Ending a swing in a circle on the left/right side facing up/down doesn't make sense.");
|
|
} else if (facing === "Across") {
|
|
facing = "In";
|
|
}
|
|
|
|
return this.fromSides(side,
|
|
(side === CircleSide.Left) !== (swingRole === DanceRole.Lark) !== (facing === "In") ? CircleSide.Bottom : CircleSide.Top);
|
|
} else {
|
|
if (facing === Facing.Up || facing === Facing.Down) {
|
|
facing = (facing === Facing.Up) === (side === CircleSide.Top) ? "Out" : "In";
|
|
} else if (facing === "Across") {
|
|
throw new Error("Ending a swing in a circle on the top/bottom side facing across doesn't make sense.");
|
|
}
|
|
|
|
return this.fromSides(
|
|
(side === CircleSide.Top) !== (swingRole === DanceRole.Lark) !== (facing === "In") ? CircleSide.Left : CircleSide.Right,
|
|
side);
|
|
}
|
|
}
|
|
|
|
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 swapOnSide(side: CircleSide) : CirclePosition {
|
|
if (side === CircleSide.Bottom || side === CircleSide.Top) {
|
|
return this.swapAcross();
|
|
} else {
|
|
return this.swapUpAndDown();
|
|
}
|
|
}
|
|
|
|
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 isOnLeft(around: CircleSide, facing: Facing.Down | Facing.Up | Facing.Left | Facing.Right | Facing.CenterOfCircle | "Out") {
|
|
if (facing === Facing.CenterOfCircle) {
|
|
facing = facingInAround(around);
|
|
} else if (facing === "Out") {
|
|
facing = oppositeFacing(facingInAround(around));
|
|
}
|
|
|
|
if (around === CircleSide.Bottom || around === CircleSide.Top) {
|
|
if (facing === Facing.Left || facing === Facing.Right) {
|
|
throw new Error("Cannot face " + facing + " and have a left/right side around " + around);
|
|
}
|
|
|
|
return (around === CircleSide.Bottom) !== (facing === Facing.Down) !== (this.isLeft());
|
|
} else {
|
|
if (facing === Facing.Up || facing === Facing.Down) {
|
|
throw new Error("Cannot face " + facing + " and have a left/right side around " + around);
|
|
}
|
|
|
|
return (around === CircleSide.Left) !== (facing === Facing.Right) !== (this.isTop());
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public static fromSide(side: CircleSide.Left | CircleSide.Right, which: "Far" | "Middle") {
|
|
if (side === CircleSide.Left) {
|
|
return which === "Far" ? ShortLinesPosition.FarLeft : ShortLinesPosition.MiddleLeft;
|
|
} else {
|
|
return which === "Far" ? ShortLinesPosition.FarRight : ShortLinesPosition.MiddleRight;
|
|
}
|
|
}
|
|
|
|
public static fromSwing(side: "Center" | CircleSide.Left | CircleSide.Right, swingRole: DanceRole, facing: Facing.Up | Facing.Down) {
|
|
if (side === "Center") {
|
|
return this.fromSide(
|
|
(swingRole === DanceRole.Lark) !== (facing === Facing.Down) ? CircleSide.Right : CircleSide.Left,
|
|
"Middle");
|
|
} else {
|
|
return this.fromSide(side,
|
|
(side === CircleSide.Left) !== (swingRole === DanceRole.Lark) !== (facing === Facing.Down) ? "Far" : "Middle");
|
|
}
|
|
}
|
|
|
|
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 { newPos, wrap } = this.shiftWithWrap(dir, facing);
|
|
if (wrap) {
|
|
throw new Error("Invalid shift: " + this + " facing " + facing + " to " + dir + ".");
|
|
}
|
|
|
|
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 {
|
|
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 facingAcross() : Facing.Left | Facing.Right {
|
|
return this.isLeft() ? Facing.Right : Facing.Left;
|
|
}
|
|
|
|
public isToLeftOf(otherPos: ShortLinesPosition): boolean {
|
|
return this.enumValue < otherPos.enumValue;
|
|
}
|
|
|
|
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.Up | Facing.Down;
|
|
export function oppositeFacing(facing: Facing.Left | Facing.Right) : Facing.Left | Facing.Right;
|
|
export function oppositeFacing(facing: Facing.LeftInCircle | Facing.RightInCircle) : Facing.LeftInCircle | Facing.RightInCircle;
|
|
export function oppositeFacing(facing: Facing.Up | Facing.Down | Facing.Left | Facing.Right) : Facing.Up | Facing.Down | Facing.Left | Facing.Right;
|
|
export function oppositeFacing(facing: Facing.Up | Facing.Down | Facing.Left | Facing.Right | Facing.LeftInCircle | Facing.RightInCircle) : Facing {
|
|
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 function isFacingUpOrDown(facing: Facing | "Across"): facing is Facing.Up | Facing.Down {
|
|
return facing === Facing.Up || facing === Facing.Down;
|
|
}
|
|
export function facingInAround(side: CircleSide) : Facing.Down | Facing.Up | Facing.Left | Facing.Right {
|
|
switch (side) {
|
|
case CircleSide.Bottom:
|
|
return Facing.Up;
|
|
break;
|
|
case CircleSide.Top:
|
|
return Facing.Down;
|
|
break;
|
|
case CircleSide.Left:
|
|
return Facing.Right;
|
|
break;
|
|
case CircleSide.Right:
|
|
return Facing.Left;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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<Hand, HandConnection>,
|
|
balance?: BalanceWeight,
|
|
dancerDistance?: DancerDistance,
|
|
longLines?: LongLines,
|
|
} | {
|
|
kind: PositionKind.ShortLines,
|
|
setOffset?: number,
|
|
lineOffset?: number,
|
|
which: ShortLinesPosition,
|
|
facing: Facing,
|
|
hands?: Map<Hand, HandConnection>,
|
|
balance?: BalanceWeight,
|
|
dancerDistance?: DancerDistance,
|
|
longLines?: undefined,
|
|
};
|
|
|
|
export const handsInCircle = new Map<Hand, HandConnection>([
|
|
[Hand.Left, {
|
|
to: HandTo.LeftInCircle,
|
|
hand: Hand.Right,
|
|
}],
|
|
[Hand.Right, {
|
|
to: HandTo.RightInCircle,
|
|
hand: Hand.Left,
|
|
}],
|
|
]);
|
|
export const handsFourImproper: Map<DancerIdentity, SemanticPosition & { kind: PositionKind.Circle }> = new Map<DancerIdentity, SemanticPosition & { kind: PositionKind.Circle }>([
|
|
[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 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 }],
|
|
]);
|
|
}
|
|
|
|
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 handsInLongLines(args.wavy);
|
|
}
|
|
}
|
|
export function handToDancerToSideInCircleFacingAcross(which: CirclePosition): Map<Hand, HandConnection> {
|
|
return new Map<Hand, HandConnection>([
|
|
which.isOnLeftLookingAcross()
|
|
? [Hand.Right, { hand: Hand.Left, to: HandTo.DancerRight }]
|
|
: [Hand.Left, { hand: Hand.Right, to: HandTo.DancerLeft }]
|
|
]);
|
|
}
|
|
export function handToDancerToSideInCircleFacingUpOrDown(which: CirclePosition): Map<Hand, HandConnection> {
|
|
return new Map<Hand, HandConnection>([
|
|
which.isOnLeftLookingUpAndDown()
|
|
? [Hand.Right, { hand: Hand.Left, to: HandTo.DancerRight }]
|
|
: [Hand.Left, { hand: Hand.Right, to: HandTo.DancerLeft }]
|
|
]);
|
|
}
|
|
|
|
export function facingAdjacent(pos: SemanticPosition, otherPos: SemanticPosition): Facing | undefined {
|
|
if (pos.kind !== otherPos.kind) {
|
|
return undefined;
|
|
}
|
|
|
|
if (pos.kind === PositionKind.ShortLines) {
|
|
if (otherPos.kind !== PositionKind.ShortLines) {
|
|
return undefined;
|
|
}
|
|
if ((pos.setOffset ?? 0) !== (otherPos.setOffset ?? 0)) {
|
|
return undefined;
|
|
}
|
|
if ((pos.lineOffset ?? 0) !== (otherPos.lineOffset ?? 0)) {
|
|
if (pos.which === ShortLinesPosition.FarLeft && otherPos.which === ShortLinesPosition.FarRight
|
|
&& ((pos.lineOffset ?? 0) - 1) === (otherPos.lineOffset ?? 0)) {
|
|
return Facing.Left;
|
|
} else if (pos.which === ShortLinesPosition.FarRight && otherPos.which === ShortLinesPosition.FarLeft
|
|
&& ((pos.lineOffset ?? 0) + 1) === (otherPos.lineOffset ?? 0)) {
|
|
return Facing.Right;
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
if (otherPos.which.isToLeftOf(pos.which)) {
|
|
return Facing.Left;
|
|
} else {
|
|
return Facing.Right;
|
|
}
|
|
} else if (pos.kind === PositionKind.Circle) {
|
|
if (otherPos.kind !== PositionKind.Circle) {
|
|
return undefined;
|
|
}
|
|
|
|
if ((pos.lineOffset ?? 0) !== (otherPos.lineOffset ?? 0)) {
|
|
// TODO
|
|
return undefined;
|
|
}
|
|
if ((pos.setOffset ?? 0) !== (otherPos.setOffset ?? 0)) {
|
|
// TODO
|
|
return undefined;
|
|
}
|
|
|
|
if (pos.which.leftRightSide() === otherPos.which.leftRightSide()) {
|
|
if (pos.which.topBottomSide() === otherPos.which.topBottomSide()) {
|
|
return undefined;
|
|
}
|
|
|
|
if ((pos.lineOffset ?? 0) !== (otherPos.lineOffset ?? 0)) {
|
|
return undefined;
|
|
}
|
|
|
|
if ((pos.setOffset ?? 0) === (otherPos.setOffset ?? 0)) {
|
|
return pos.which.facingUpOrDown();
|
|
} else if (((pos.setOffset ?? 0) + 1) === (otherPos.setOffset ?? 0) && pos.which.isTop()) {
|
|
return Facing.Up;
|
|
} else if (((pos.setOffset ?? 0) - 1) === (otherPos.setOffset ?? 0) && !pos.which.isTop()) {
|
|
return Facing.Down;
|
|
}
|
|
} else if (pos.which.topBottomSide() === otherPos.which.topBottomSide()) {
|
|
if ((pos.setOffset ?? 0) === (otherPos.setOffset ?? 0)) {
|
|
return undefined;
|
|
}
|
|
|
|
if ((pos.lineOffset ?? 0) !== (otherPos.lineOffset ?? 0)) {
|
|
return pos.which.facingAcross();
|
|
} else if (((pos.lineOffset ?? 0) + 1) === (otherPos.lineOffset ?? 0) && pos.which.isLeft()) {
|
|
return Facing.Left;
|
|
} else if (((pos.lineOffset ?? 0) - 1) === (otherPos.lineOffset ?? 0) && !pos.which.isLeft()) {
|
|
return Facing.Right;
|
|
}
|
|
} else {
|
|
// Opposite corners of circle.
|
|
return undefined;
|
|
}
|
|
} else {
|
|
throw new Error("Unexpected PositionKind: " + otherPos.kind);
|
|
}
|
|
}
|
|
|
|
export function facingRequireAdjacent(pos: SemanticPosition, otherPos: SemanticPosition, moveName: string): Facing {
|
|
const res = facingAdjacent(pos, otherPos);
|
|
|
|
if (!res) {
|
|
throw new Error("Cannot " + moveName + " when not adjacent to paired dancer.");
|
|
}
|
|
|
|
return res;
|
|
} |