From fc8d8ab8d0da95ec79056f59e14a150df6fcea89 Mon Sep 17 00:00:00 2001 From: Karan Jayachandra Date: Sun, 30 Aug 2026 22:43:21 +0200 Subject: Moved to svelte --- src/lib/stores/courts.svelte.js | 39 +++++++++++++ src/lib/stores/games.svelte.js | 91 ++++++++++++++++++++++++++++++ src/lib/stores/guests.svelte.js | 46 +++++++++++++++ src/lib/stores/regulars.svelte.js | 79 ++++++++++++++++++++++++++ src/lib/stores/skillFactor.svelte.js | 9 +++ src/lib/stores/timer.svelte.js | 106 +++++++++++++++++++++++++++++++++++ 6 files changed, 370 insertions(+) create mode 100644 src/lib/stores/courts.svelte.js create mode 100644 src/lib/stores/games.svelte.js create mode 100644 src/lib/stores/guests.svelte.js create mode 100644 src/lib/stores/regulars.svelte.js create mode 100644 src/lib/stores/skillFactor.svelte.js create mode 100644 src/lib/stores/timer.svelte.js (limited to 'src/lib/stores') 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(); +} -- cgit v1.3.1