universal-siteswap
Version:
A library for parsing, validating, examining and finding transitions between all types of siteswaps
268 lines • 9.61 kB
JavaScript
export function toLetter(n, base) {
return String.fromCharCode(base.charCodeAt(0) + n);
}
export function fromLetter(letter, base) {
return letter.charCodeAt(0) - base.charCodeAt(0);
}
export function intToSS(n) {
if (0 <= n && n < 10) {
return n.toString();
}
else if (10 <= n && n < 36) {
return toLetter(n - 10, 'a');
}
throw new Error('Only siteswaps up to height 35 are accepted');
}
export function floatToSS(n) {
var intPart = Math.floor(n);
var floatStr = (n - intPart).toLocaleString(undefined, {
maximumFractionDigits: 2,
});
if (floatStr === '1') {
// Rounded up, e.g. was 4.999
return intToSS(intPart + 1);
}
return floatStr.replace(/^0/, intToSS(intPart));
}
export function ssToInt(ss) {
if (ss.length === 1) {
if ('0' <= ss && ss <= '9') {
return parseInt(ss);
}
else if ('a' <= ss && ss <= 'z') {
return fromLetter(ss, 'a') + 10;
}
}
throw new Error('Unknown siteswap throw "' + ss + '"');
}
export function ssToFloat(ss) {
var intPart = ssToInt(ss[0]);
if (ss.length > 1) {
var floatSS = ss.slice(1);
if (!/^\.[0-9]+$/.test(floatSS)) {
throw new Error('Unknown siteswap throw "' + ss + '"');
}
return intPart + parseFloat(floatSS);
}
return intPart;
}
export var Hand;
(function (Hand) {
Hand[Hand["Right"] = 0] = "Right";
Hand[Hand["Left"] = 1] = "Left";
})(Hand || (Hand = {}));
var EPS = 1e-7;
// Lax epsilon to deal with inputs which are only 2 decimal places normally
var LAX_EPS = 1e-2;
// Deal with fractional throw heights
export function fixFraction(n, allow36) {
if (allow36 === void 0) { allow36 = false; }
var whole = Math.floor(n);
var frac = n % 1;
if (frac === 0)
return n;
if (allow36 && Math.abs(frac - 0.3) < EPS)
return whole + 1 / 3;
if (allow36 && Math.abs(frac - 0.6) < EPS)
return whole + 2 / 3;
for (var i = 2; i < 10; i++) {
for (var num = 1; num < i; num++) {
if (Math.abs(frac - num / i) < LAX_EPS) {
return whole + num / i;
}
}
}
return n;
}
export function unfixFraction(n, allow36) {
if (allow36 === void 0) { allow36 = false; }
var whole = Math.floor(n);
var frac = n % 1;
if (frac === 0)
return n;
if (allow36 && Math.abs(frac - 1 / 3) < EPS)
return whole + 0.3;
if (allow36 && Math.abs(frac - 2 / 3) < EPS)
return whole + 0.6;
// Otherwise, just round to 2 decimal places
return Math.round(n * 100) / 100;
}
export function allPositions(numJugglers, period) {
var positions = [];
for (var j = 0; j < numJugglers; j++) {
for (var i = 0; i < period; i++) {
for (var _i = 0, _a = [Hand.Right, Hand.Left]; _i < _a.length; _i++) {
var hand = _a[_i];
positions.push({ juggler: j, time: i, hand: hand });
}
}
}
return positions;
}
// A single throw, has a height, which juggler it's to and whether or not it has an 'x'
var Throw = /** @class */ (function () {
function Throw(height, x, pass, passTo) {
this.dispHeight = height;
this.height = fixFraction(height, /*allow36=*/ false);
this.x = x;
this.pass = pass;
this.passTo = passTo;
}
Throw.prototype.toString = function () {
return [
floatToSS(this.dispHeight),
this.pass ? 'p' : '',
this.x ? 'x' : '',
this.passTo != null ? toLetter(this.passTo, 'A') : '',
].join('');
};
Throw.prototype.throwSwapsHands = function () {
// Passes swap hands normally ('straight') and with an x they don't ('crossing')
if (this.pass)
return !this.x;
// Normal throws swap hands if they're even with an x or odd with no x
return (this.height % 2 === 0) === this.x;
};
Throw.prototype.landJuggler = function (startJuggler, numJugglers) {
if (this.pass) {
return this.passTo != null
? this.passTo
: (startJuggler + 1) % numJugglers;
}
return startJuggler;
};
Throw.prototype.clone = function () {
return new Throw(this.dispHeight, this.x, this.pass, this.passTo);
};
Throw.FromPositions = function (p1, p2) {
var height = p2.time - p1.time;
var pass = p1.juggler !== p2.juggler;
var passTo = pass ? p2.juggler : undefined;
var swapsHands = p1.hand === p2.hand;
var x = pass ? swapsHands : (height % 2 === 0) !== swapsHands;
return new Throw(height, x, pass, passTo);
};
return Throw;
}());
export { Throw };
// On a beat, each hand of a juggler can do a multiplex with any number of throws.
var JugglerBeat = /** @class */ (function () {
function JugglerBeat(LH, RH) {
this.LH = LH;
this.RH = RH;
}
JugglerBeat.prototype.isSync = function () {
return this.LH.length > 0 && this.RH.length > 0;
};
JugglerBeat.prototype.isEmpty = function () {
return this.LH.length === 0 && this.RH.length === 0;
};
JugglerBeat.prototype.isAsync = function () {
return !this.isSync() && !this.isEmpty();
};
JugglerBeat.prototype.maxMultiplex = function () {
return Math.max(this.LH.length, this.RH.length);
};
JugglerBeat.prototype.hasPass = function () {
return this.LH.some(function (th) { return th.pass; }) || this.RH.some(function (th) { return th.pass; });
};
JugglerBeat.prototype.flip = function () {
return new JugglerBeat(this.RH, this.LH);
};
JugglerBeat.prototype.clone = function () {
return new JugglerBeat(this.LH.map(function (th) { return th.clone(); }), this.RH.map(function (th) { return th.clone(); }));
};
JugglerBeat.prototype.toString = function (nextHand) {
var leftThrows = this.LH.map(function (th) { return th.toString(); }).join('');
var rightThrows = this.RH.map(function (th) { return th.toString(); }).join('');
var left = this.LH.length > 1 ? "[".concat(leftThrows, "]") : leftThrows;
var right = this.RH.length > 1 ? "[".concat(rightThrows, "]") : rightThrows;
if (left && right) {
return "(".concat(left, ",").concat(right, ")");
}
else if (left) {
return nextHand === Hand.Left ? left : "L".concat(left);
}
else if (right) {
return nextHand === Hand.Right ? right : "R".concat(right);
}
else {
return '';
}
};
return JugglerBeat;
}());
export { JugglerBeat };
var JugglerBeats = /** @class */ (function () {
// Constructor deals with multiple types to make the parser cleaner.
// If there is a Throw or Throw[], its hand is given as the opposite of the previous hand, resetting to RH at the beginning and after sync beats.
function JugglerBeats(beats, repeatFlipped) {
var _a;
if (repeatFlipped === void 0) { repeatFlipped = false; }
this.beats = [];
for (var _i = 0, beats_1 = beats; _i < beats_1.length; _i++) {
var beat = beats_1[_i];
if (beat instanceof JugglerBeat) {
this.beats.push(beat);
}
else {
var prev = this.beats.slice(-1)[0];
// Must be not at the start, and have only RH throwing previously
if (prev && prev.isAsync() && prev.RH.length) {
this.beats.push(new JugglerBeat(beat, []));
}
else {
this.beats.push(new JugglerBeat([], beat));
}
}
}
if (repeatFlipped) {
(_a = this.beats).push.apply(_a, this.beats.map(function (beat) { return beat.flip(); }));
}
}
JugglerBeats.prototype.flip = function () {
return new JugglerBeats(this.beats.map(function (beat) { return beat.flip(); }));
};
JugglerBeats.prototype.clone = function () {
return new JugglerBeats(this.beats.map(function (beat) { return beat.clone(); }));
};
JugglerBeats.prototype.toString = function () {
var prevSync = false;
var result = '';
var curHand = Hand.Right;
for (var _i = 0, _a = this.beats; _i < _a.length; _i++) {
var beat = _a[_i];
if (prevSync && !beat.isEmpty()) {
result += '!';
}
var curString = beat.toString(curHand);
if ((curString === 'x' || curString === 'p') && result !== '') {
result += ' ';
}
result += curString;
// If we have an empty beat that isn't hidden treat as async 0
if (beat.isEmpty() && !prevSync) {
result += '0';
// Keep in line with whatever async was thrown previously
curHand = 1 - curHand;
}
else if (beat.isEmpty()) {
curHand = Hand.Right;
}
else if (beat.isSync()) {
curHand = Hand.Right;
}
else if (beat.isAsync()) {
curHand = beat.RH.length ? Hand.Left : Hand.Right;
}
prevSync = beat.isSync();
}
if (prevSync) {
result += '!';
}
return result;
};
return JugglerBeats;
}());
export { JugglerBeats };
//# sourceMappingURL=common.js.map