When correcting for a timezone being ahead of UTC the start has to be moved backwards in time, not forward. Fixes the generated schodule not using central european times.
273 lines
7.3 KiB
TypeScript
273 lines
7.3 KiB
TypeScript
import { Account } from "~/shared/types/account";
|
|
import { Schedule, TimeSlot } from "~/shared/types/schedule";
|
|
|
|
const locations = [
|
|
{
|
|
name: "Stage",
|
|
description: "Inside the main building."
|
|
},
|
|
{
|
|
name: "Clubhouse",
|
|
description: "That big red building in the middle of the park."
|
|
},
|
|
{
|
|
name: "Summerhouse",
|
|
description: "Next to the campfire by the lake"
|
|
},
|
|
{
|
|
name: "Campfire",
|
|
description: "Next to the big tree by the lake."
|
|
},
|
|
{
|
|
name: "Outside",
|
|
description: "Takes place somewhere outside."
|
|
}
|
|
]
|
|
|
|
const events = [
|
|
{
|
|
name: "Arcade",
|
|
description: "Play retro games!",
|
|
slots: [
|
|
"d1 12:00 4h clubhouse",
|
|
"d2 12:00 4h clubhouse",
|
|
"d3 12:00 4h clubhouse",
|
|
],
|
|
},
|
|
{ name: "Arcade Setup", crew: true, slots: ["d1 11:00 1h clubhouse"], },
|
|
{
|
|
name: "Bonfire Stories",
|
|
description: "Share your stories as we sit cosily around the campfire.",
|
|
slots: ["d2 18:00 2h campfire"],
|
|
},
|
|
{ name: "Stage Rigging", crew: true, slots: ["d1 11:00 7h30m stage"]},
|
|
{
|
|
name: "Reconfigure for DJ",
|
|
crew: true,
|
|
slots: [
|
|
"d2 19:00 1h stage",
|
|
"d3 18:45 1h15m stage",
|
|
],
|
|
},
|
|
{ name: "DJ Alpha", slots: ["d2 20:00 2h stage"] },
|
|
{ name: "DJ Bravo", slots: ["d3 20:00 2h stage"] },
|
|
{ name: "DJ Charlie", slots: ["d3 22:00 2h stage"] },
|
|
{ name: "Prepare Fursuit Games", crew: true, slots: ["d4 17:00 1h clubhouse"] },
|
|
{
|
|
name: "Fursuit Games",
|
|
description: "Playful time for the suiters.",
|
|
slots: ["d4 18:00 2h clubhouse"],
|
|
},
|
|
{ name: "Fishing Trip", slots: ["d3 12:00 3h30m outside"]},
|
|
{ name: "Opening", slots: ["d1 18:30 1h30m stage"]},
|
|
{ name: "Closing", slots: ["d5 16:00 1h stage"]},
|
|
{ name: "Stage Teardown", crew: true, slots: ["d5 17:00 4h stage"]},
|
|
{ name: "Setup Board Games", crew: true, slots: ["d1 11:30 30m summerhouse"]},
|
|
{
|
|
name: "Board Games",
|
|
slots: [
|
|
"d1 12:00 4h summerhouse",
|
|
"d2 12:00 4h summerhouse",
|
|
"d3 12:00 4h summerhouse",
|
|
"d4 12:00 4h summerhouse",
|
|
],
|
|
},
|
|
{ name: "Teardown Board Games", crew: true, slots: ["d4 16:00 30m summerhouse"]},
|
|
{ name: "📷meet", slots: ["d3 19:00 1h10m summerhouse"]},
|
|
{
|
|
name: "Prepare Karaoke",
|
|
crew: true,
|
|
slots: [
|
|
"d3 20:00 1h clubhouse",
|
|
"d4 20:00 1h clubhouse",
|
|
],
|
|
},
|
|
{
|
|
name: "Karaoke",
|
|
slots: [
|
|
"d3 21:00 2h clubhouse",
|
|
"d4 21:00 2h clubhouse",
|
|
],
|
|
},
|
|
{ name: "Dance", slots: ["d1 20:00 3h stage"]},
|
|
{ name: "Prepare Charity Auction", crew: true, slots: ["d4 10:00 3h stage"]},
|
|
{ name: "Charity Auction", slots: ["d4 13:00 2h stage"]},
|
|
{ name: "Tournament Preparation", crew: true, slots: ["d2 15:00 2h stage"]},
|
|
{ name: "Tournament", slots: ["d2 17:00 2h stage"]},
|
|
{ name: "Canoe Trip", slots: ["d4 11:00 4h30m outside"]},
|
|
{
|
|
name: "Dinner Preparations",
|
|
crew: true,
|
|
slots: [
|
|
"d1 14:00 2h campfire",
|
|
"d2 14:00 2h campfire",
|
|
"d3 14:00 2h campfire",
|
|
"d4 14:00 2h campfire",
|
|
],
|
|
},
|
|
{
|
|
name: "Dinner",
|
|
slots: [
|
|
"d1 16:00 1h campfire",
|
|
"d2 16:00 1h campfire",
|
|
"d3 16:00 1h campfire",
|
|
"d4 16:00 1h campfire",
|
|
],
|
|
},
|
|
{
|
|
name: "Dinner Cleanup",
|
|
crew: true,
|
|
slots: [
|
|
"d1 17:00 1h campfire",
|
|
"d2 17:00 1h campfire",
|
|
"d3 17:00 1h campfire",
|
|
"d4 17:00 1h campfire",
|
|
],
|
|
},
|
|
{ name: "Prepare Film Night", crew: true, slots: ["d4 19:00 2h stage"]},
|
|
{ name: "Film Night", slots: ["d4 21:00 2h stage"]},
|
|
{ name: "Prepare Group Photo", crew: true, slots: ["d3 17:00 1h stage"]},
|
|
{ name: "Group Photo", slots: ["d3 18:00 45m stage"]},
|
|
{ name: "Setup Artist Alley", crew: true, slots: ["d4 10:00 2h clubhouse"]},
|
|
{ name: "Artist Alley", slots: ["d4 12:00 4h clubhouse"]},
|
|
{ name: "Teardown Artist Alley", crew: true, slots: ["d4 16:00 1h clubhouse"]},
|
|
{ name: "Feedback Panel", slots: ["d5 18:00 1h clubhouse"]},
|
|
];
|
|
|
|
function toId(name: string) {
|
|
return name.toLowerCase().replace(/ /g, "-");
|
|
}
|
|
|
|
function toIso(date: Date) {
|
|
return date.toISOString().replace(":00.000Z", "Z");
|
|
}
|
|
|
|
function toSlot(origin: Date, id: string, shorthand: string, index: number, counts: Map<string, number>): TimeSlot {
|
|
const [day, start, duration, location] = shorthand.split(" ");
|
|
const [startHours, startMinutes] = start.split(":").map(time => parseInt(time, 10));
|
|
const dayNumber = parseInt(day.slice(1));
|
|
|
|
const startDate = new Date(origin);
|
|
startDate.setUTCDate(startDate.getUTCDate() + dayNumber);
|
|
startDate.setUTCHours(startDate.getUTCHours() + startHours);
|
|
startDate.setUTCMinutes(startDate.getUTCMinutes() + startMinutes);
|
|
|
|
const [_, durationHours, durationMinutes] = /(?:(\d+)h)?(?:(\d+)m)?/.exec(duration)!;
|
|
const durationTotal = parseInt(durationHours ?? "0") * 60 + parseInt(durationMinutes ?? "0")
|
|
const endDate = new Date(startDate.getTime() + durationTotal * 60e3);
|
|
|
|
return {
|
|
id: `${id}-${index}`,
|
|
start: toIso(startDate),
|
|
end: toIso(endDate),
|
|
locations: [location],
|
|
interested: counts.get(`${id}-${index}`),
|
|
};
|
|
}
|
|
|
|
export function generateDemoSchedule(): Schedule {
|
|
const origin = new Date();
|
|
const utcOffset = 1;
|
|
origin.setUTCDate(origin.getUTCDate() - origin.getUTCDay() + 1); // Go to Monday
|
|
origin.setUTCHours(-utcOffset);
|
|
origin.setUTCMinutes(0);
|
|
origin.setUTCSeconds(0);
|
|
origin.setUTCMilliseconds(0);
|
|
|
|
const counts = new Map<string, number>()
|
|
for (const account of generateDemoAccounts()) {
|
|
for (const id of account.interestedIds ?? []) {
|
|
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
}
|
|
}
|
|
|
|
return {
|
|
events: events.map(
|
|
({ name, crew, description, slots }) => ({
|
|
id: toId(name),
|
|
name,
|
|
crew,
|
|
description,
|
|
interested: counts.get(toId(name)),
|
|
slots: slots.map((shorthand, index) => toSlot(origin, toId(name), shorthand, index, counts))
|
|
})
|
|
),
|
|
locations: locations.map(
|
|
({ name, description }) => ({ id: toId(name), name, description })
|
|
),
|
|
};
|
|
}
|
|
|
|
const names = [
|
|
"Leo", "Lisa",
|
|
"Jack", "Emily",
|
|
"Roy", "Sofia",
|
|
"Adam", "Eve",
|
|
"Max", "Rose",
|
|
"Hugo", "Maria",
|
|
"David", "Zoe",
|
|
"Hunter", "Ria",
|
|
"Sonny", "Amy",
|
|
"Kai", "Megan",
|
|
"Toby", "Katie",
|
|
"Bob", "Lucy",
|
|
];
|
|
|
|
// MINSTD random implementation for reproducible random numbers.
|
|
let seed = 1;
|
|
function random() {
|
|
const a = 48271;
|
|
const c = 0;
|
|
const m = 2 ** 31 -1;
|
|
return (seed = (a * seed + c) % m | 0) / 2 ** 31;
|
|
}
|
|
|
|
export function generateDemoAccounts(): Account[] {
|
|
seed = 1;
|
|
const accounts: Account[] = [];
|
|
|
|
for (const name of names) {
|
|
accounts.push({
|
|
id: accounts.length,
|
|
name,
|
|
type: (["regular", "crew", "admin"] as const)[Math.floor(random() ** 5 * 3)],
|
|
});
|
|
}
|
|
|
|
// These have a much higher probability of being in someone's interested list.
|
|
const desiredEvent = ["opening", "closing", "fursuit-games"];
|
|
const nonCrewEvents = events.filter(event => !event.crew);
|
|
|
|
for (const account of accounts) {
|
|
const interestedIds: string[] = [];
|
|
for (const id of desiredEvent) {
|
|
if (random() < 0.5) {
|
|
interestedIds.push(id);
|
|
}
|
|
}
|
|
|
|
const eventsToAdd = Math.floor(random() * 10);
|
|
while (interestedIds.length < eventsToAdd) {
|
|
const event = nonCrewEvents[Math.floor(random() * nonCrewEvents.length)];
|
|
const eventId = toId(event.name);
|
|
if (interestedIds.some(id => id.replace(/-\d+$/, "") === eventId)) {
|
|
continue;
|
|
}
|
|
|
|
if (event.slots.length === 1 || random() < 0.8) {
|
|
interestedIds.push(toId(event.name))
|
|
} else {
|
|
for (const index of event.slots.map((_, index) => index)) {
|
|
if (random() < 0.5) {
|
|
interestedIds.push(toId(`${toId(event.name)}-${index}`));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (interestedIds.length) {
|
|
account.interestedIds = interestedIds;
|
|
}
|
|
}
|
|
return accounts;
|
|
}
|