aboutsummaryrefslogtreecommitdiff
path: root/src/lib/stores
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/stores')
-rw-r--r--src/lib/stores/courts.svelte.js39
-rw-r--r--src/lib/stores/games.svelte.js91
-rw-r--r--src/lib/stores/guests.svelte.js46
-rw-r--r--src/lib/stores/regulars.svelte.js79
-rw-r--r--src/lib/stores/skillFactor.svelte.js9
-rw-r--r--src/lib/stores/timer.svelte.js106
6 files changed, 370 insertions, 0 deletions
diff --git a/src/lib/stores/courts.svelte.js b/src/lib/stores/courts.svelte.js
new file mode 100644
index 0000000..768bb35
--- /dev/null
+++ b/src/lib/stores/courts.svelte.js
@@ -0,0 +1,39 @@
+import { createCourt } from "../logic/players.js";
+import { randomStatus } from "../logic/random.js";
+
+const STORAGE_KEY = "Court";
+const COURT_COUNT = 12;
+
+function loadInitialCourts() {
+ const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
+ if (stored && stored.length > 0) {
+ return stored.map((c) => createCourt(Number(c.id), Boolean(Number(c.status))));
+ }
+ return Array.from({ length: COURT_COUNT }, (_, i) => createCourt(i, randomStatus()));
+}
+
+let courts = $state(loadInitialCourts());
+
+function persist() {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(courts));
+}
+if (!localStorage.getItem(STORAGE_KEY)) persist();
+
+export function getCourts() {
+ return courts;
+}
+
+export function activeCourts() {
+ return courts.filter((c) => c.status);
+}
+
+export function inactiveCourts() {
+ return courts.filter((c) => !c.status);
+}
+
+export function toggleCourt(id) {
+ const court = courts.find((c) => c.id === id);
+ if (!court) return;
+ court.status = !court.status;
+ persist();
+}
diff --git a/src/lib/stores/games.svelte.js b/src/lib/stores/games.svelte.js
new file mode 100644
index 0000000..ae76ce3
--- /dev/null
+++ b/src/lib/stores/games.svelte.js
@@ -0,0 +1,91 @@
+import { createGame, createTeam } from "../logic/players.js";
+import {
+ proposeGames,
+ createDefaultGames,
+ collectConfirmedPlayerNames,
+} from "../logic/matchmaking.js";
+import {
+ getCourts,
+} from "./courts.svelte.js";
+import {
+ getRegulars,
+ incrementRegularGames,
+ resetRegulars,
+} from "./regulars.svelte.js";
+import { getGuests, incrementGuestGames } from "./guests.svelte.js";
+import { getSkillFactor } from "./skillFactor.svelte.js";
+import { start as startTimer, stopTimer } from "./timer.svelte.js";
+
+const GAMES_STORAGE_KEY = "Game";
+const ROUND_STORAGE_KEY = "round";
+const GAME_COUNT = 12;
+
+function loadInitialGames() {
+ const stored = JSON.parse(localStorage.getItem(GAMES_STORAGE_KEY));
+ if (stored && stored.length > 0) {
+ return stored.map((g) =>
+ createGame(
+ Number(g.id),
+ Boolean(g.status),
+ createTeam(g.teamOne.playerOne, g.teamOne.playerTwo),
+ createTeam(g.teamTwo.playerOne, g.teamTwo.playerTwo)
+ )
+ );
+ }
+ return createDefaultGames(GAME_COUNT);
+}
+
+let games = $state(loadInitialGames());
+let round = $state(Number(localStorage.getItem(ROUND_STORAGE_KEY)) || 0);
+
+function persistGames() {
+ localStorage.setItem(GAMES_STORAGE_KEY, JSON.stringify(games));
+}
+
+export function getGames() {
+ return games;
+}
+
+export function getRound() {
+ return round;
+}
+
+export function getGameDescription() {
+ const confirmed = games[0]?.status;
+ return confirmed ? `Round ${round}` : "Proposal";
+}
+
+export function propose() {
+ games = proposeGames({
+ courts: getCourts(),
+ regulars: getRegulars(),
+ guests: getGuests(),
+ skillFactor: getSkillFactor(),
+ });
+}
+
+export function confirm() {
+ const alreadyConfirmed = games[0]?.status;
+ if (alreadyConfirmed) return;
+
+ round += 1;
+ localStorage.setItem(ROUND_STORAGE_KEY, round);
+ for (const game of games) game.status = true;
+
+ const selectedNames = collectConfirmedPlayerNames(games);
+ incrementRegularGames(selectedNames);
+ incrementGuestGames(selectedNames);
+
+ persistGames();
+ startTimer(true);
+}
+
+export function resetSession() {
+ localStorage.removeItem(ROUND_STORAGE_KEY);
+ localStorage.removeItem(GAMES_STORAGE_KEY);
+ localStorage.removeItem("Court");
+ localStorage.removeItem("Guest");
+ resetRegulars();
+ stopTimer();
+ window.location.reload();
+}
diff --git a/src/lib/stores/guests.svelte.js b/src/lib/stores/guests.svelte.js
new file mode 100644
index 0000000..6dc08db
--- /dev/null
+++ b/src/lib/stores/guests.svelte.js
@@ -0,0 +1,46 @@
+import { createGuest } from "../logic/players.js";
+import { randomStatus } from "../logic/random.js";
+
+const STORAGE_KEY = "Guest";
+const GUEST_COUNT = 18;
+
+function loadInitialGuests() {
+ const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
+ if (stored && stored.length > 0) {
+ return stored.map((g) =>
+ createGuest(Number(g.id), Boolean(Number(g.status)), Number(g.games))
+ );
+ }
+ return Array.from({ length: GUEST_COUNT }, (_, i) => createGuest(i, randomStatus()));
+}
+
+let guests = $state(loadInitialGuests());
+
+function persist() {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(guests));
+}
+if (!localStorage.getItem(STORAGE_KEY)) persist();
+
+export function getGuests() {
+ return guests;
+}
+
+export function activeGuests() {
+ return guests.filter((g) => g.status);
+}
+
+export function toggleGuest(id) {
+ const guest = guests.find((g) => g.id === id);
+ if (!guest) return;
+ guest.status = !guest.status;
+ persist();
+}
+
+export function incrementGuestGames(names) {
+ for (const guest of guests) {
+ if (names.includes(guest.name)) {
+ guest.games += 1;
+ }
+ }
+ persist();
+}
diff --git a/src/lib/stores/regulars.svelte.js b/src/lib/stores/regulars.svelte.js
new file mode 100644
index 0000000..ccf0d81
--- /dev/null
+++ b/src/lib/stores/regulars.svelte.js
@@ -0,0 +1,79 @@
+import { uniqueNamesGenerator, names, adjectives } from "unique-names-generator";
+import { createRegular } from "../logic/players.js";
+import { randomStatus, randomLevel } from "../logic/random.js";
+import { parseCsv } from "../logic/csv.js";
+
+const STORAGE_KEY = "Regular";
+const REGULAR_COUNT = 71;
+
+const nameConfig = {
+ dictionaries: [adjectives, names],
+ style: "capital",
+ length: 2,
+};
+
+function generateRandomRegular(id) {
+ const [firstName, lastName] = uniqueNamesGenerator(nameConfig).split("_");
+ return createRegular(id, randomStatus(), randomLevel(), 0, firstName, lastName);
+}
+
+function loadInitialRegulars() {
+ const stored = JSON.parse(localStorage.getItem(STORAGE_KEY));
+ if (stored && stored.length > 0) {
+ return stored.map((r) =>
+ createRegular(
+ Number(r.id),
+ Boolean(Number(r.status)),
+ Number(r.level),
+ Number(r.games),
+ r.firstName,
+ r.lastName
+ )
+ );
+ }
+ return Array.from({ length: REGULAR_COUNT }, (_, i) => generateRandomRegular(i));
+}
+
+let regulars = $state(loadInitialRegulars());
+
+function persist() {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(regulars));
+}
+if (!localStorage.getItem(STORAGE_KEY)) persist();
+
+export function getRegulars() {
+ return regulars;
+}
+
+export function activeRegulars() {
+ return regulars.filter((r) => r.status);
+}
+
+export function toggleRegular(id) {
+ const regular = regulars.find((r) => r.id === id);
+ if (!regular) return;
+ regular.status = !regular.status;
+ persist();
+}
+
+export function incrementRegularGames(playerNames) {
+ for (const regular of regulars) {
+ if (playerNames.includes(regular.name)) {
+ regular.games += 1;
+ }
+ }
+ persist();
+}
+
+export function resetRegulars() {
+ for (const regular of regulars) {
+ regular.games = 0;
+ regular.status = false;
+ }
+ persist();
+}
+
+export function loadRegularsFromCsv(csvText) {
+ regulars = parseCsv(csvText);
+ persist();
+}
diff --git a/src/lib/stores/skillFactor.svelte.js b/src/lib/stores/skillFactor.svelte.js
new file mode 100644
index 0000000..d031040
--- /dev/null
+++ b/src/lib/stores/skillFactor.svelte.js
@@ -0,0 +1,9 @@
+let skillFactor = $state(10); // "All Levels" — matches the #skill select's original default
+
+export function getSkillFactor() {
+ return skillFactor;
+}
+
+export function setSkillFactor(value) {
+ skillFactor = Number(value);
+}
diff --git a/src/lib/stores/timer.svelte.js b/src/lib/stores/timer.svelte.js
new file mode 100644
index 0000000..dea1799
--- /dev/null
+++ b/src/lib/stores/timer.svelte.js
@@ -0,0 +1,106 @@
+import { Notyf } from "notyf";
+
+const DEFAULT_MINS = 12;
+const MIN_MINS = 0.5;
+
+const initialMins = Number(sessionStorage.getItem("mins")) || DEFAULT_MINS;
+const initialRunning = Boolean(Number(sessionStorage.getItem("running")));
+const initialDeadline = Number(sessionStorage.getItem("deadline")) || Date.now();
+const initialRemainingMs = initialRunning
+ ? Math.max(initialDeadline - Date.now(), 0)
+ : initialMins * 60 * 1000;
+
+let mins = $state(initialMins);
+let running = $state(initialRunning);
+let deadline = $state(initialDeadline);
+let remainingMs = $state(initialRemainingMs);
+
+let intervalId = null;
+
+function persist() {
+ sessionStorage.setItem("mins", mins);
+ sessionStorage.setItem("running", Number(running));
+ sessionStorage.setItem("deadline", deadline);
+}
+
+function showRoundCompletedToast() {
+ const notyf = new Notyf({
+ duration: 0,
+ position: { x: "center", y: "top" },
+ dismissible: true,
+ });
+ notyf.error("Round completed");
+}
+
+function stopInterval() {
+ if (intervalId !== null) clearInterval(intervalId);
+ intervalId = null;
+}
+
+function tick() {
+ const msToDeadline = deadline - Date.now();
+ if (msToDeadline > 0) {
+ remainingMs = msToDeadline;
+ return;
+ }
+ stopInterval();
+ running = false;
+ remainingMs = mins * 60 * 1000;
+ persist();
+ showRoundCompletedToast();
+}
+
+export function getMins() {
+ return mins;
+}
+
+export function getRunning() {
+ return running;
+}
+
+export function getRemainingMs() {
+ return remainingMs;
+}
+
+export function formatRemaining(ms) {
+ const m = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
+ const s = Math.floor((ms % (1000 * 60)) / 1000);
+ const pad = (n) => (n < 10 ? "0" + n : String(n));
+ return `${pad(m)}:${pad(s)}`;
+}
+
+export function getDisplay() {
+ return formatRemaining(remainingMs);
+}
+
+export function stopTimer() {
+ stopInterval();
+ running = false;
+ remainingMs = mins * 60 * 1000;
+ persist();
+}
+
+export function start(fresh = true) {
+ if (fresh && running) return;
+ if (fresh) {
+ deadline = Date.now() + mins * 60 * 1000;
+ }
+ running = true;
+ remainingMs = Math.max(deadline - Date.now(), 0);
+ persist();
+ stopInterval();
+ intervalId = setInterval(tick, 1000);
+}
+
+export function changeMinutes(delta) {
+ mins = Math.max(mins + delta, MIN_MINS);
+ stopTimer();
+}
+
+// Resume a timer that was still running across a page reload, otherwise
+// settle the display to the resting (not-running) state.
+if (initialRunning) {
+ start(false);
+} else {
+ stopTimer();
+}