Files
root/artwork-schedule.js

51 lines
2.1 KiB
JavaScript

(() => {
'use strict';
const SLOT_DURATION = 137 * 60 * 1000;
// Mulberry32: 같은 Seed는 항상 0 이상 1 미만의 동일한 난수열을 생성합니다.
function createRandom(seed) {
let state = seed >>> 0;
return () => {
state = (state + 0x6D2B79F5) >>> 0;
let value = Math.imul(state ^ (state >>> 15), state | 1);
value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
};
}
function rawOrder(loop, count) {
const random = createRandom(loop);
const order = Array.from({ length: count }, (_, index) => index);
for (let index = count - 1; index > 0; index--) {
const other = Math.floor(random() * (index + 1));
[order[index], order[other]] = [order[other], order[index]];
}
return order;
}
function orderForLoop(loop, count = 30) {
if (!Number.isInteger(count) || count < 3) throw new RangeError('At least three artworks are required.');
if (!Number.isSafeInteger(loop) || loop < 0) throw new RangeError('Invalid loop number.');
const order = rawOrder(loop, count);
if (loop > 0) {
// 항목이 3개 이상이면 첫 두 위치를 교환해도 마지막 항목은 바뀌지 않습니다.
// 따라서 이전 루프의 보정 전 마지막 항목을 그대로 비교할 수 있습니다.
// 재귀 호출, 상태 저장, 과거 루프 전체 순회는 필요하지 않습니다.
const previousLast = rawOrder(loop - 1, count)[count - 1];
if (order[0] === previousLast) [order[0], order[1]] = [order[1], order[0]];
}
return order;
}
function selectionAt(time, count = 30) {
if (!Number.isFinite(time) || time < 0) throw new RangeError('Invalid timestamp.');
const slot = Math.floor(time / SLOT_DURATION);
const loop = Math.floor(slot / count);
const position = slot % count;
return { slot, loop, position, variant: orderForLoop(loop, count)[position] };
}
globalThis.NNCArtworkSchedule = Object.freeze({ SLOT_DURATION, createRandom, orderForLoop, selectionAt });
})();