roulette / js /bag.js
ratandeep's picture
Deploy Build Small Roulette — static space serendipity engine for the org
653b61d
Raw
History Blame Contribute Delete
2.02 kB
export function shuffle(arr, rng = Math.random) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
export class Bag {
constructor(ids, { rng = Math.random, state = null } = {}) {
this.rng = rng;
if (state && Array.isArray(state.bag) && state.bag.length) {
this.ids = [...ids];
this.bag = state.bag;
this.cursor = state.cursor ?? 0;
this.round = state.round ?? 1;
this.history = [];
this.histPos = -1;
this.lastShown = null;
} else {
this.setIds(ids);
}
}
setIds(ids) {
this.ids = [...ids];
this.bag = shuffle(this.ids, this.rng);
this.cursor = 0;
this.round = 1;
this.history = [];
this.histPos = -1;
this.lastShown = null;
}
_pop() {
if (this.cursor >= this.bag.length) {
// new round: reshuffle, avoid immediate repeat of lastShown
this.round += 1;
this.cursor = 0;
if (this.ids.length > 1) {
do { this.bag = shuffle(this.ids, this.rng); }
while (this.bag[0] === this.lastShown);
} else {
this.bag = shuffle(this.ids, this.rng);
}
}
return this.bag[this.cursor++];
}
next() {
// if we stepped back, going next replays forward through history first
if (this.histPos < this.history.length - 1) {
this.histPos += 1;
this.lastShown = this.history[this.histPos];
return this.lastShown;
}
const id = this._pop();
this.history.push(id);
this.histPos = this.history.length - 1;
this.lastShown = id;
return id;
}
prev() {
if (this.histPos <= 0) return null;
this.histPos -= 1;
this.lastShown = this.history[this.histPos];
return this.lastShown;
}
progress() {
return { seen: Math.min(this.cursor, this.bag.length), total: this.bag.length, round: this.round };
}
serialize() {
return { bag: this.bag, cursor: this.cursor, round: this.round };
}
}