universal-siteswap
Version:
A library for parsing, validating, examining and finding transitions between all types of siteswaps
423 lines • 17.4 kB
JavaScript
"use strict";
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.State = exports.JugglerState = exports.JugglerStateBeat = void 0;
var common_1 = require("./common");
var siteswap_1 = require("./siteswap");
var JugglerStateBeat = /** @class */ (function () {
function JugglerStateBeat(LH, RH) {
if (LH === void 0) { LH = 0; }
if (RH === void 0) { RH = 0; }
this.LH = LH;
this.RH = RH;
}
JugglerStateBeat.prototype.increment = function (hand) {
if (hand === common_1.Hand.Left)
this.LH++;
else
this.RH++;
};
JugglerStateBeat.prototype.decrement = function (hand) {
if (hand === common_1.Hand.Left)
this.LH--;
else
this.RH--;
};
JugglerStateBeat.prototype.val = function (hand) {
return hand === common_1.Hand.Left ? this.LH : this.RH;
};
JugglerStateBeat.prototype.isSync = function () {
return this.LH > 0 && this.RH > 0;
};
JugglerStateBeat.prototype.isEmpty = function () {
return this.LH === 0 && this.RH === 0;
};
JugglerStateBeat.prototype.flip = function () {
return new JugglerStateBeat(this.RH, this.LH);
};
JugglerStateBeat.prototype.isLessOrEqual = function (other) {
return this.LH <= other.LH && this.RH <= other.RH;
};
JugglerStateBeat.prototype.toString = function (sync) {
if (sync) {
return "(".concat(this.LH, ",").concat(this.RH, ")");
}
else {
if (this.isSync()) {
throw new Error('Attempt to use async toString on sync beat');
}
// One of them must be zero so this is valid.
return "".concat(this.LH + this.RH);
}
};
return JugglerStateBeat;
}());
exports.JugglerStateBeat = JugglerStateBeat;
var JugglerState = /** @class */ (function () {
function JugglerState(beats) {
this.beats = beats;
}
JugglerState.Empty = function (maxHeight) {
var beats = [];
for (var i = 0; i < maxHeight; i++) {
beats.push(new JugglerStateBeat());
}
return new JugglerState(beats);
};
JugglerState.prototype.removeTrailingZeros = function () {
while (this.beats.length && this.beats[this.beats.length - 1].isEmpty()) {
this.beats.pop();
}
};
JugglerState.prototype.isPureAsync = function () {
// Async patterns can start with either hand
var curHand = -1;
for (var _i = 0, _a = this.beats; _i < _a.length; _i++) {
var beat = _a[_i];
if (beat.isSync())
return false;
if (beat.LH > 0) {
if (curHand === common_1.Hand.Right)
return false;
curHand = common_1.Hand.Left;
}
else if (beat.RH > 0) {
if (curHand === common_1.Hand.Left)
return false;
curHand = common_1.Hand.Right;
}
if (curHand !== -1) {
curHand = 1 - curHand;
}
}
return true;
};
JugglerState.prototype.flip = function () {
return new JugglerState(this.beats.map(function (j) { return j.flip(); }));
};
JugglerState.prototype.toString = function () {
var isAsync = this.isPureAsync();
return this.beats.map(function (b) { return b.toString(!isAsync); }).join('');
};
return JugglerState;
}());
exports.JugglerState = JugglerState;
function siteswapShorter(s1, s2) {
return (s1.period < s2.period ||
(s1.period === s2.period && s1.toString().length < s2.toString().length));
}
var State = /** @class */ (function () {
function State(jugglers, trimZeros, jugglerDelays) {
if (trimZeros === void 0) { trimZeros = true; }
this.isGround = false;
this.numObjects = 0;
this.numJugglers = 0;
this.maxHeight = 0;
this.jugglers = jugglers;
this.numJugglers = this.jugglers.length;
this.jugglerDelays = jugglerDelays
? jugglerDelays
: new Array(jugglers.length).fill(0);
if (trimZeros)
this.trimZeros();
this.recalc();
}
State.prototype.recalc = function () {
this.numObjects = 0;
this.maxHeight = 0;
this.isGround = true;
var len = this.numJugglers > 0 ? this.jugglers[0].beats.length : 0;
for (var _i = 0, _a = this.jugglers; _i < _a.length; _i++) {
var state = _a[_i];
// All jugglers must be within a margin of 1
this.isGround && (this.isGround = Math.abs(state.beats.length - len) <= 1);
this.isGround && (this.isGround = state.isPureAsync());
this.maxHeight = Math.max(this.maxHeight, state.beats.length);
for (var _b = 0, _c = state.beats; _b < _c.length; _b++) {
var beat = _c[_b];
this.numObjects += beat.LH + beat.RH;
this.isGround && (this.isGround = beat.LH + beat.RH === 1);
}
}
};
State.prototype.trimZeros = function () {
for (var _i = 0, _a = this.jugglers; _i < _a.length; _i++) {
var state = _a[_i];
state.removeTrailingZeros();
}
};
State.prototype.at = function (position) {
var beat = this.jugglers[position.juggler].beats[position.time];
return beat === undefined ? 0 : beat.val(position.hand);
};
State.prototype.inc = function (position) {
this.jugglers[position.juggler].beats[position.time].increment(position.hand);
};
State.prototype.dec = function (position) {
this.jugglers[position.juggler].beats[position.time].decrement(position.hand);
};
State.prototype.flip = function () {
return new State(this.jugglers.map(function (j) { return j.flip(); }));
};
State.prototype.toString = function () {
if (this.numJugglers === 1)
return this.jugglers[0].toString();
var stateStr = this.jugglers.map(function (j) { return j.toString(); }).join('|');
return "<".concat(stateStr, ">");
};
State.prototype.entry = function (from, sync, allowFlipped) {
if (sync === void 0) { sync = false; }
if (allowFlipped === void 0) { allowFlipped = true; }
if (!from) {
from = State.GroundState(this.numObjects, sync, this.numJugglers);
}
return State.ShortestTransition(from, this, allowFlipped);
};
State.prototype.exit = function (to, sync, allowFlipped) {
if (sync === void 0) { sync = false; }
if (allowFlipped === void 0) { allowFlipped = true; }
if (!to) {
to = State.GroundState(this.numObjects, sync, this.numJugglers);
}
return State.ShortestTransition(this, to, allowFlipped);
};
State.prototype.globalStateBeat = function (time) {
return this.jugglers.map(function (state) {
return time < state.beats.length ? state.beats[time] : new JugglerStateBeat(0, 0);
});
};
/*
makeThrow(beat: JugglerBeat[]) {
const front = this.state[0];
const nonZeros = beat.reduce((tot, cur) => tot + (cur != 0 ? 1 : 0), 0);
if (nonZeros != front) {
throw Error(`${front} non-zero throws expected but ${nonZeros} given.`);
}
const newState = this.state.slice(1);
for (const th of beat) {
while (newState.length < th) {
newState.push(0);
}
if (th != 0) {
newState[th-1]++;
}
}
return new State(newState);
}
*/
State.GroundState = function (numObjects, sync, numJugglers) {
if (sync === void 0) { sync = false; }
if (numJugglers === void 0) { numJugglers = 1; }
var jugglers = [];
var minNumObjects = Math.floor(numObjects / numJugglers);
var maxNumObjects = Math.ceil(numObjects / numJugglers);
for (var i = 0; i < numJugglers; i++) {
var n = i < numObjects % numJugglers ? maxNumObjects : minNumObjects;
var state = JugglerState.Empty(n);
if (sync) {
for (var j = 0; j < n; j += 2) {
state.beats[j].increment(common_1.Hand.Right);
if (n % 2 === 0 || j !== n - 1) {
state.beats[j].increment(common_1.Hand.Left);
}
}
}
else {
var curHand = common_1.Hand.Left;
for (var j = 0; j < n; j++) {
state.beats[j].increment(curHand);
curHand = 1 - curHand;
}
}
jugglers.push(state);
}
return new State(jugglers);
};
State.Empty = function (numJugglers, maxHeight) {
var state = [];
for (var j = 0; j < numJugglers; j++) {
state.push(JugglerState.Empty(maxHeight));
}
return new State(state, /*trimZeros=*/ false);
};
State.IsTransitionValid = function (s1, s2) {
if (s1.numObjects !== s2.numObjects) {
throw Error('States must be for the same number of objects.');
}
if (s1.numJugglers !== s2.numJugglers) {
throw Error('States must be for the same number of jugglers.');
}
// TODO: there really should be a built in way of comparing arrays...
if (!s1.jugglerDelays.every(function (v, i) { return v === s2.jugglerDelays[i]; })) {
throw Error('Transitions between states with different juggler delays are not supported.');
}
};
State.IsShiftValid = function (s1, s2, shift) {
var _loop_1 = function (i) {
var globalBeat1 = s1.globalStateBeat(shift + i);
var globalBeat2 = s2.globalStateBeat(i);
if (!globalBeat1.every(function (beat, i) { return beat.isLessOrEqual(globalBeat2[i]); })) {
return { value: false };
}
};
// Shift is valid when s2 is >= s1 at all points, e.g.
// 11011
// 11101
for (var i = 0; i < Math.min(s2.maxHeight, s1.maxHeight - shift); i++) {
var state_1 = _loop_1(i);
if (typeof state_1 === "object")
return state_1.value;
}
return true;
};
State.ShortestTransitionLength = function (s1, s2) {
State.IsTransitionValid(s1, s2);
var shift = Math.max(0, s1.maxHeight - s2.maxHeight);
for (; shift <= s1.maxHeight; shift++) {
if (State.IsShiftValid(s1, s2, shift)) {
return shift;
}
}
// If they have the same number of objects, we should have found a valid shift.
/* istanbul ignore next */
throw Error('Logic Error - this should never happen');
};
State.FindLandings = function (s1, s2, shift) {
// Find landing times/positions needed
// 11011
// 11101
// Lands: 2, 6
var lands = [];
for (var _i = 0, _a = (0, common_1.allPositions)(s1.numJugglers, s2.maxHeight); _i < _a.length; _i++) {
var pos = _a[_i];
var newLanding = s2.at(pos);
pos.time += shift;
var alreadyLanding = pos.time >= s1.maxHeight ? 0 : s1.at(pos);
for (var j = 0; j < newLanding - alreadyLanding; j++) {
lands.push(pos);
}
}
return lands;
};
State.GetTransition = function (s, length, land_times) {
var upto = 0;
var jugglers = [];
for (var j = 0; j < s.numJugglers; j++) {
var jugglerBeats = [];
for (var i = 0; i < length; i++) {
var beat = [[], []];
for (var _i = 0, _a = [common_1.Hand.Right, common_1.Hand.Left]; _i < _a.length; _i++) {
var hand = _a[_i];
if (s.jugglers[j].beats[i] == null) {
continue;
}
for (var k = 0; k < s.jugglers[j].beats[i].val(hand); k++) {
var start = { juggler: j, time: i, hand: hand };
beat[hand].push(common_1.Throw.FromPositions(start, land_times[upto]));
upto++;
}
}
jugglerBeats.push(new common_1.JugglerBeat(beat[0], beat[1]));
}
jugglers.push(new common_1.JugglerBeats(jugglerBeats));
}
return new siteswap_1.Siteswap(jugglers);
};
State.ShortestTransition = function (s1, s2, allowFlipped, allowExtraFlipped) {
if (allowExtraFlipped === void 0) { allowExtraFlipped = false; }
var best = State.BasicTransition(s1, s2);
if (allowFlipped) {
var flipA = State.BasicTransition(s1.flip(), s2);
var flipB = State.BasicTransition(s1, s2.flip());
var flipAB = State.BasicTransition(s1.flip(), s2.flip());
if (siteswapShorter(flipA, best))
best = flipA;
if (siteswapShorter(flipB, best))
best = flipB;
if (siteswapShorter(flipAB, best))
best = flipAB;
}
if (allowExtraFlipped) {
// Try flipping jugglers around and flipping hands of some jugglers
// TODO: do this
}
return best;
};
State.BasicTransition = function (s1, s2) {
var length = State.ShortestTransitionLength(s1, s2);
var lands = State.FindLandings(s1, s2, length);
// Match landing positions to throw positions to get throws.
// 0 -> 2 ::: 2
// 1 -> 6 ::: 5
return State.GetTransition(s1, length, lands);
};
State.AllTransitionsOfLength = function (s1, s2, length) {
var land_times, perm_length, c, i, k, p, t;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
State.IsTransitionValid(s1, s2);
if (!State.IsShiftValid(s1, s2, length)) {
return [2 /*return*/];
}
land_times = State.FindLandings(s1, s2, length);
return [4 /*yield*/, State.GetTransition(s1, length, land_times)];
case 1:
_a.sent();
perm_length = land_times.length;
c = new Array(perm_length).fill(0);
i = 1;
_a.label = 2;
case 2:
if (!(i < perm_length)) return [3 /*break*/, 6];
if (!(c[i] < i)) return [3 /*break*/, 4];
k = i % 2 && c[i];
p = land_times[i];
land_times[i] = land_times[k];
land_times[k] = p;
++c[i];
i = 1;
t = State.GetTransition(s1, length, land_times);
return [4 /*yield*/, t];
case 3:
_a.sent();
return [3 /*break*/, 5];
case 4:
c[i] = 0;
++i;
_a.label = 5;
case 5: return [3 /*break*/, 2];
case 6: return [2 /*return*/];
}
});
};
return State;
}());
exports.State = State;
//# sourceMappingURL=state.js.map