UNPKG

universal-siteswap

Version:

A library for parsing, validating, examining and finding transitions between all types of siteswaps

499 lines 24.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Siteswap = void 0; var common_1 = require("./common"); var parser_1 = require("./parser"); var state_1 = require("./state"); var vanilla_siteswap_1 = require("./vanilla-siteswap"); function np(a, b) { return "".concat(a, ",").concat(b); } function round3(num) { return Math.round((num + Number.EPSILON) * 1000) / 1000; } function swapHand(time, period, implicitFlip) { var loops = Math.floor(time / period); return implicitFlip && loops % 2 === 1; } var Siteswap = /** @class */ (function () { function Siteswap(jugglers, jugglerDelays) { this.numObjects = 0; this.numJugglers = 0; this.period = 0; this.maxHeight = 0; this.maxMultiplex = 0; this.hasSync = false; this.hasAsync = false; this.hasPass = false; this.pureAsync = false; this.isValid = false; this.errorMessage = ''; this._jugglerDelaysInferred = false; this._implicitFlip = false; this.jugglers = jugglers; this.jugglerDelays = jugglerDelays ? jugglerDelays : new Array(jugglers.length).fill(-1); // If we have a multiple of 3 jugglers then fix and .3 and .6 throws if (this.jugglers.length % 3 === 0) { for (var _i = 0, _a = this.jugglers; _i < _a.length; _i++) { var juggler = _a[_i]; for (var _b = 0, _c = juggler.beats; _b < _c.length; _b++) { var beat = _c[_b]; for (var _d = 0, _e = [beat.LH, beat.RH]; _d < _e.length; _d++) { var ths = _e[_d]; for (var _f = 0, ths_1 = ths; _f < ths_1.length; _f++) { var th = ths_1[_f]; th.height = (0, common_1.fixFraction)(th.dispHeight, /*allow36=*/ true); } } } } } if (this.jugglers.length > 0) { // Convert any delays of more than 1 to less and rotate the siteswap this.pureAsync = this.jugglers.every(function (juggler) { return juggler.beats.every(function (beat) { return beat.isAsync(); }); }); // Pure async siteswaps of odd period automatically flip sides this._implicitFlip = this.pureAsync && this.jugglers[0].beats.length % 2 === 1; for (var i = 0; i < this.jugglerDelays.length; i++) { if (this.jugglerDelays[i] >= 1) { this.jugglers[i] = this.jugglers[i].clone(); } while (this.jugglerDelays[i] >= 1) { var lastBeat = this.jugglers[i].beats.pop(); if (this._implicitFlip) lastBeat = lastBeat.flip(); this.jugglers[i].beats.unshift(lastBeat); this.jugglerDelays[i]--; } } } // Make compiler happy this.state = new state_1.State([], true, jugglerDelays); this.validate(); } Siteswap.prototype.inferJugglerDelays = function () { if (!this.jugglerDelays.every(function (x) { return x === -1; })) { // We were given delays, don't infer them return true; } this.jugglerDelays[0] = 0; var allowed_jugglers = [0]; while (allowed_jugglers.length > 0) { var juggler = allowed_jugglers.pop(); for (var i = 0; i < this.period; i++) { for (var _i = 0, _a = [common_1.Hand.Right, common_1.Hand.Left]; _i < _a.length; _i++) { var hand = _a[_i]; var pos = { juggler: juggler, time: i, hand: hand }; for (var _b = 0, _c = this.throwsAt(pos); _b < _c.length; _b++) { var th = _c[_b]; if (th.pass) { var to = th.passTo != null ? th.passTo : (juggler + 1) % this.numJugglers; var land_frac = (this.jugglerDelays[juggler] + th.height) % 1; if (this.jugglerDelays[to] === -1) { this.jugglerDelays[to] = land_frac; allowed_jugglers.push(to); } else if (Math.abs(this.jugglerDelays[to] - land_frac) > 1e-2) { var jugglerName = (0, common_1.toLetter)(juggler, 'A'); this.errorMessage = "Cannot find consistent juggler delays: juggler ".concat(jugglerName, " has delays of ").concat(this.jugglerDelays[to], " and ").concat(land_frac); return false; } } else { if (th.height % 1 !== 0) { this.errorMessage = "Cannot find consistent juggler delays: self throw of height ".concat(th.height); return false; } } } } } } this._jugglerDelaysInferred = true; return true; }; Siteswap.prototype.throwsAt = function (position) { var beat = this.jugglers[position.juggler].beats[position.time]; return position.hand === common_1.Hand.Right ? beat.RH : beat.LH; }; Siteswap.prototype.validate = function () { var _this = this; this.isValid = false; this.errorMessage = ''; this.numJugglers = this.jugglers.length; if (this.numJugglers === 0) { this.errorMessage = 'No jugglers'; return false; } this.period = this.jugglers[0].beats.length; if (!this.jugglers.every(function (juggler) { return juggler.beats.length === _this.period; })) { this.errorMessage = 'Mismatching juggler periods'; return false; } if (!this.inferJugglerDelays()) { // Couldn't find consistent juggler delays return false; } // Convert .3 and .6 to 1/3 and 2/3 when we have a multiple of 3 jugglers var allow36 = this.numJugglers % 3 === 0; this.jugglerDelays = this.jugglerDelays.map(function (d) { return (0, common_1.fixFraction)(d, allow36); }); if (this.jugglerDelays.length !== this.numJugglers) { this.errorMessage = "Number of jugglers (".concat(this.numJugglers, ") not equal to number of delays (").concat(this.jugglerDelays.length, ")"); return false; } this.maxMultiplex = Math.max.apply(Math, this.jugglers.map(function (j) { return Math.max.apply(Math, j.beats.map(function (b) { return b.maxMultiplex(); })); })); this.hasPass = this.jugglers.some(function (j) { return j.beats.some(function (b) { return b.hasPass(); }); }); this.hasSync = this.jugglers.some(function (j) { return j.beats.some(function (b) { return b.isSync(); }); }); this.hasAsync = this.jugglers.some(function (j) { return j.beats.some(function (b) { return b.isAsync(); }); }); this.maxHeight = 0; var sum = 0; // `check` stores the number of throws which should land for each juggler, beat and hand var check = state_1.State.Empty(this.numJugglers, this.period); for (var _i = 0, _a = (0, common_1.allPositions)(this.numJugglers, this.period); _i < _a.length; _i++) { var pos = _a[_i]; for (var _b = 0, _c = this.throwsAt(pos); _b < _c.length; _b++) { var th = _c[_b]; sum += th.height; this.maxHeight = Math.max(this.maxHeight, th.height); // Validate pass recipients if (th.passTo && th.passTo >= this.numJugglers) { var juggler = (0, common_1.toLetter)(th.passTo, 'A'); this.errorMessage = "Invalid juggler ".concat(juggler, ", there are only ").concat(this.numJugglers, " jugglers"); return false; } check.inc(pos); } } this.numObjects = sum / this.period; // Be a bit more lax and round if we have juggler delays var rounded = Math.round(this.numObjects); if (this.jugglerDelays.some(function (d) { return d !== 0; }) && Math.abs(this.numObjects - rounded) < 1e-2) { this.numObjects = rounded; } if (this.numObjects % 1 !== 0) { this.errorMessage = 'Invalid pattern average'; return false; } this.state = state_1.State.Empty(this.numJugglers, this.maxHeight); for (var _d = 0, _e = (0, common_1.allPositions)(this.numJugglers, this.period); _d < _e.length; _d++) { var pos = _e[_d]; for (var _f = 0, _g = this.throwsAt(pos); _f < _g.length; _f++) { var th = _g[_f]; var landJuggler = th.landJuggler(pos.juggler, this.numJugglers); var fractionDiff = this.jugglerDelays[pos.juggler] - this.jugglerDelays[landJuggler]; var fullLandTime = pos.time + th.height + fractionDiff; var rounded_1 = Math.round(fullLandTime); if (Math.abs(fullLandTime - rounded_1) < 1e-2) { fullLandTime = rounded_1; } else { this.errorMessage = "Throw ".concat(th.dispHeight, " lands at an invalid time for juggler ").concat(landJuggler); return false; } var landTime = fullLandTime % this.period; var fullLandHand = th.throwSwapsHands() ? 1 - pos.hand : pos.hand; // We swap hands (again) if there is an implicit flip (e.g. odd period // vanilla siteswaps) and we looped around an odd number of times. // This isn't because the throw swapped hands but because the siteswap // should be repeating on the other side. var landHand = swapHand(fullLandTime, this.period, this._implicitFlip) ? 1 - fullLandHand : fullLandHand; var landPosition = { juggler: landJuggler, time: landTime, hand: landHand, }; // Check landing position if (check.at(landPosition) <= 0) { this.errorMessage = "Collision at juggler ".concat(landJuggler, ", time ").concat(landTime, ", hand ").concat(landHand); return false; } check.dec(landPosition); // Add to state, first at original land time, ignoring those landing before the end of the siteswap var curTime = fullLandTime - this.period; var curHand = fullLandHand; while (curTime >= 0) { this.state.inc({ juggler: landJuggler, time: curTime, hand: curHand }); // Back one period of the siteswap if (this._implicitFlip) curHand = 1 - curHand; curTime -= this.period; } } } this.state.trimZeros(); this.state.recalc(); this.isValid = true; return true; }; Siteswap.prototype.toString = function () { if (this.jugglers.length === 1) { return this.jugglers[0].toString(); } else { var jugglerStrings = this.jugglers.map(function (j) { return j.toString(); }).join('|'); var allow36_1 = this.numJugglers % 3 === 0; var delays = ''; if (this.jugglerDelays.some(function (d) { return d > 0; }) && !this._jugglerDelaysInferred) { delays = "{".concat(this.jugglerDelays.map(function (d) { return (0, common_1.unfixFraction)(d, allow36_1); }).join(','), "}"); } return "".concat(delays, "<").concat(jugglerStrings, ">"); } }; Siteswap.prototype.flip = function () { return new Siteswap(this.jugglers.map(function (juggler) { return juggler.flip(); })); }; Siteswap.prototype.toVanilla = function () { if (!this.pureAsync || this.numJugglers !== 1) { throw "Could not convert to vanilla siteswap: must be async and single juggler"; } var throws = []; for (var _i = 0, _a = this.jugglers[0].beats; _i < _a.length; _i++) { var beat = _a[_i]; var ths = beat.LH.length > 0 ? beat.LH : beat.RH; throws.push(ths.map(function (th) { return th.height; })); } return new vanilla_siteswap_1.VanillaSiteswap(throws); }; Siteswap.prototype.toJIF = function () { var _this = this; var RADIUS = this.numJugglers === 1 ? 0 : 1.5; var objectType = "club"; var jugglers = []; var limbs = []; var props = []; for (var i = 0; i < this.numJugglers; i++) { limbs.push({ juggler: i, type: 'right hand' }); limbs.push({ juggler: i, type: 'left hand' }); var angle = i * 2 * Math.PI / this.numJugglers; jugglers.push({ name: (0, common_1.toLetter)(i, 'A'), position: [round3(RADIUS * Math.cos(angle)), 0, round3(RADIUS * Math.sin(angle))], lookAt: [0, 0, this.numJugglers === 1 ? 1 : 0], }); } for (var i = 0; i < this.numObjects; i++) { props.push({ color: "#f45d20", type: objectType }); } var events = []; for (var _i = 0, _a = (0, common_1.allPositions)(this.numJugglers, this.period); _i < _a.length; _i++) { var pos = _a[_i]; for (var _b = 0, _c = this.throwsAt(pos); _b < _c.length; _b++) { var th = _c[_b]; if (th.height == 0) { continue; } events.push({ time: pos.time + this.jugglerDelays[pos.juggler], height: th.height, fromHand: pos.hand, toHand: th.throwSwapsHands() ? 1 - pos.hand : pos.hand, fromJuggler: pos.juggler, toJuggler: th.landJuggler(pos.juggler, this.numJugglers), label: th.toString(), }); } } events.sort(function (a, b) { return a.time - b.time; }); var jif; var useold = true; if (useold) { // Use old code until passist is updated properly var throws = []; var positions = new Map(); var timeOffset = 0; var nextProp = 0; var inits = []; while (true) { for (var _d = 0, events_1 = events; _d < events_1.length; _d++) { var event_1 = events_1[_d]; var time = event_1["time"] + timeOffset; var fromHand = swapHand(time, this.period, this._implicitFlip) ? 1 - event_1["fromHand"] : event_1["fromHand"]; var toHand = swapHand(time, this.period, this._implicitFlip) ? 1 - event_1["toHand"] : event_1["toHand"]; var th = { time: time, from: event_1["fromJuggler"] * 2 + fromHand, to: event_1["toJuggler"] * 2 + toHand, duration: event_1["height"], label: event_1["label"], }; if (positions.get(np(th.time, th.from)) == undefined) { positions.set(np(th.time, th.from), nextProp); inits.push([th.time, th.from, nextProp]); nextProp += 1; } th.prop = positions.get(np(th.time, th.from)); positions.set(np(th.time + th.duration, th.to), positions.get(np(th.time, th.from))); positions.set(np(th.time, th.from), undefined); throws.push(th); } timeOffset += this.period; var equal = true; for (var _e = 0, inits_1 = inits; _e < inits_1.length; _e++) { var _f = inits_1[_e], time = _f[0], from = _f[1], prop = _f[2]; if (positions.get(np(time + timeOffset, from)) !== prop) { equal = false; break; } } if (equal) break; if (timeOffset > 500) { /* istanbul ignore next */ throw "Could not find a repetition within 500 steps, giving up"; } } jif = { "meta": { "name": this.toString(), "type": "General siteswap", "description": this.toString(), "generator": "siteswap.js", "version": "0.01", }, "timeStretchFactor": 1, "jugglers": jugglers, "limbs": limbs, "props": props, "throws": throws, "repetition": { "period": timeOffset }, }; } else { // New code which relies on passist completing things as it does in JIF edit interface var throws = []; for (var _g = 0, events_2 = events; _g < events_2.length; _g++) { var event_2 = events_2[_g]; var th = { time: event_2["time"], from: event_2["fromJuggler"] * 2 + event_2["fromHand"], to: event_2["toJuggler"] * 2 + event_2["toHand"], duration: event_2["height"], label: event_2["label"], }; throws.push(th); } var limbPermutation = Array.from({ length: limbs.length }, function (_, i) { return _this._implicitFlip ? i ^ 1 : i; }); jif = { "meta": { "name": this.toString(), "type": "General siteswap", "description": this.toString(), "generator": "siteswap.js", "version": "0.01", }, "timeStretchFactor": 1, "jugglers": jugglers, "limbs": limbs, "props": props, "throws": throws, "repetition": { "period": this.period, "limbPermutation": limbPermutation, }, }; } ; return jif; }; Siteswap.FromJif = function (jif) { if (jif.limbs.some(function (l) { return l.type !== "right hand" && l.type !== "left hand"; })) { throw "Conversion from JIF only implemented with right and left hands"; } var timeStretchFactor = jif.timeStretchFactor == null ? 1 : jif.timeStretchFactor; var jugglers = jif.jugglers.map(function (j) { return new common_1.JugglerBeats(Array.from(new Array(jif.repetition.period), function () { return []; })); }); var jugglerDelays = jif.jugglers.map(function (j) { return 0; }); var events = jif.throws; for (var _i = 0, events_3 = events; _i < events_3.length; _i++) { var event_3 = events_3[_i]; var fromJuggler = jif.limbs[event_3.from].juggler; var fromHand = jif.limbs[event_3.from].type === "right hand" ? common_1.Hand.Right : common_1.Hand.Left; var toJuggler = jif.limbs[event_3.to].juggler; var toHand = jif.limbs[event_3.to].type === "right hand" ? common_1.Hand.Right : common_1.Hand.Left; var eventTime = event_3.time / timeStretchFactor; var eventDuration = event_3.duration / timeStretchFactor; var delay = eventTime % 1; if (delay !== 0) { if (jugglerDelays[fromJuggler] !== 0 && jugglerDelays[fromJuggler] !== delay) { throw "Cannot handle multiple delays for the same juggler"; } jugglerDelays[fromJuggler] = delay; } var time = eventTime - jugglerDelays[fromJuggler]; var pass = fromJuggler !== toJuggler; var passTo = pass && jugglers.length > 2 ? toJuggler : undefined; var hasX = pass ? fromHand === toHand : (eventDuration % 2 === 0) !== (fromHand === toHand); var th = new common_1.Throw(eventDuration, hasX, pass, passTo); var jugglerBeat = jugglers[fromJuggler].beats[time]; if (fromHand === common_1.Hand.Right) { jugglerBeat.RH.push(th); } else { jugglerBeat.LH.push(th); } } return new Siteswap(jugglers); }; Siteswap.Parse = function (input) { var parsed = (0, parser_1.parse)(input); return new Siteswap(parsed[0], parsed[1]); }; Siteswap.ParseKHSS = function (input, hands) { if (hands === void 0) { hands = 2; } return Siteswap.FromKHSS(vanilla_siteswap_1.VanillaSiteswap.Parse(input), hands); }; Siteswap.FromKHSS = function (input, hands) { if (hands === void 0) { hands = 2; } if (input.maxMultiplex > 1) { throw "KHSS only implemented without multiplexes"; } if (hands % 2 !== 0) { // A bit of a hack to deal with odd-handed siteswaps as they break that each juggler has an even rhythm, // instead make `hands` jugglers and only use one hand from each juggler. var doubled = []; for (var _i = 0, _a = input.throws; _i < _a.length; _i++) { var th = _a[_i]; doubled.push(th.map(function (c) { return c * 2; })); doubled.push([0]); } // const doubled = Array.from(input).map(c => intToSS(ssToInt(c) * 2) + '0').join(''); return Siteswap.FromKHSS(new vanilla_siteswap_1.VanillaSiteswap(doubled), hands * 2); } var numJugglers = hands / 2; var fix36 = numJugglers % 3 === 0; var jugglers = []; for (var i_1 = 0; i_1 < numJugglers; i_1++) { jugglers.push([]); } var j = 0; var i = 0; while (i >= 0) { var fullThrow = input.throws[i][0]; // Convert to local height and make more readable var height = (0, common_1.unfixFraction)(fullThrow / numJugglers, fix36); // Non multiples are passes var isPass = fullThrow % numJugglers !== 0; // This checks the parity of the number of times the throw height crosses the last juggler var hasX = isPass && (fullThrow + j) % hands < hands / 2; // If it's not a pass to the next juggler, add who it's passed to var passTo = isPass && fullThrow % numJugglers !== 1 ? (fullThrow + j) % numJugglers : undefined; var th = new common_1.Throw(height, hasX, isPass, passTo); // The height use earlier is the nice one - this is the exact one for use internally th.height = fullThrow / numJugglers; jugglers[j].push([th]); j = (j + 1) % numJugglers; i = (i + 1) % input.throws.length; if (i === 0 && j === 0) { // We've reached the start after enough repetitions break; } } var jugglerBeats = jugglers.map(function (ths) { return new common_1.JugglerBeats(ths); }); var delays = Array.from(new Array(numJugglers), function (x, i) { return i / numJugglers; }); return new Siteswap(jugglerBeats, delays); }; return Siteswap; }()); exports.Siteswap = Siteswap; //# sourceMappingURL=siteswap.js.map