A simple daily word game of finding the the 7 letter word from 8 scrambled letters.
Install dependencies with npm install (or pnpm install or yarn), start a development server:
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --openSCRMBLD uses Cloudflare D1 for its database. Migrations are stored in the migrations/ directory and are applied using Wrangler.
Local Development (applies to your local D1 database):
pnpm db:migrate:localRemote Production (applies to the production D1 database):
pnpm db:migrate:remotePreview/Staging (applies to the staging D1 database):
pnpm db:migrate:preview- Create a new SQL file in the
migrations/directory with the naming conventionNNNN_description.sql(e.g.,0005_add_new_feature.sql) - Write your SQL statements in the file
- Run the appropriate migration command above
| File | Description |
|---|---|
0001_add-gameplay-table.sql |
Initial gameplay tracking table |
0002_Add_newsletter_signup.sql |
Newsletter signup functionality |
0003_auth_tables.sql |
User authentication tables (Better Auth) |
0004_achievements_notifications_stats.sql |
Achievements, push notifications, and stats indexes |
- Migrations are applied in order based on the numeric prefix
- Each migration runs only once (Wrangler tracks applied migrations)
- Always test migrations locally before applying to production
- The
--localflag uses the local D1 simulator bundled with Wrangler
If you see "no such table" errors after running migrations, the dev server may be using a different local database file than where migrations were applied. This can happen when multiple database files exist in .wrangler/state/v3/d1/miniflare-D1DatabaseObject/.
Solution 1: Stop the dev server, delete the .wrangler/state/ directory, then run migrations and restart:
# Stop dev server (Ctrl+C)
rm -rf .wrangler/state/
pnpm db:migrate:local
pnpm devSolution 2: Apply migrations directly to the active database file:
# Find the database file being used (usually the most recently modified)
ls -lt .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite
# Apply migration directly
sqlite3 .wrangler/state/v3/d1/miniflare-D1DatabaseObject/<filename>.sqlite < migrations/0004_achievements_notifications_stats.sqlPush notifications require VAPID keys for authentication with browser push services.
Run the key generation script:
pnpm generate:vapidThis will output:
VAPID_PUBLIC_KEY- Used by the browser to subscribe to pushVAPID_PRIVATE_KEY- Used by the server to sign push messages
Create a .dev.vars file in the project root (this file is gitignored):
VAPID_PUBLIC_KEY=your_public_key_here
VAPID_PRIVATE_KEY=your_private_key_here
NOTIFICATIONS_WORKER_URL=http://localhost:8787
Set the secrets in Cloudflare Workers (for main app):
echo "your_public_key" | wrangler secret put VAPID_PUBLIC_KEY
echo "your_private_key" | wrangler secret put VAPID_PRIVATE_KEY
echo "https://scrmbld-notifications.your-account.workers.dev" | wrangler secret put NOTIFICATIONS_WORKER_URLPush notification reminders are handled by a separate Durable Object worker that provides per-user timezone-aware scheduling. The DO uses SQLite for persistent storage and hourly alarms to check which users should receive notifications.
- Main app → notifies DO on subscribe/unsubscribe/game-played events
- DO worker → maintains its own subscription database with timezone info
- Hourly alarm → checks each hour which users' preferred notification time has arrived
- Per-user scheduling → users receive reminders at their preferred hour in their local timezone
# First, set the secrets for the notification worker
wrangler secret put VAPID_PUBLIC_KEY -c workers/notifications/wrangler.jsonc
wrangler secret put VAPID_PRIVATE_KEY -c workers/notifications/wrangler.jsonc
# Deploy the notification worker
pnpm notifications:deploycurl -X POST https://scrmbld-notifications.your-account.workers.dev/init-alarmpnpm notifications:dev
# Worker runs at http://localhost:8787pnpm notifications:tailcurl https://scrmbld-notifications.your-account.workers.dev/statusReturns subscription count, sent-today count, next alarm time, and timezone distribution.
curl -X POST https://scrmbld-notifications.your-account.workers.dev/trigger-
When a user enables notifications, the main app:
- Saves the subscription to D1 (source of truth)
- Notifies the DO worker via
/subscribewith user ID, push credentials, and timezone
-
When a user plays the daily puzzle, the main app:
- Notifies the DO worker via
/playedwith user ID and day - DO marks user as "sent today" so they won't get a reminder
- Notifies the DO worker via
-
Every hour, the DO alarm fires and:
- Checks which subscribed users have their preferred notification hour in their timezone
- Skips users who already played today or were already sent a notification
- Sends push notifications to remaining users
- Marks them as "sent today"
-
At midnight UTC, the "sent today" table is cleaned up for the new day
This SvelteKit project follows a standard directory layout. Here's a high-level overview of the main directories and their purpose within SCRMBLD:
-
src/: This directory contains the core source code of your SvelteKit application.lib/: This directory is for Svelte components (likeFlipText.svelte,Keyboard.svelte,Popover.svelte), utility functions (e.g.,math.ts,ripple.ts,tootltip.ts), and other shared modules (index.ts) that can be imported into your routes and components.server/: Code that only runs on the server-side.daily-word.server.ts: Handles logic related to fetching or managing the daily word.schema.sql: Defines the database schema for game statistics and related data.
routes/: This directory defines the routing structure of your application. Each Svelte component (+page.svelte), server-side load function (+page.server.ts), or API endpoint (+server.ts) within this directory corresponds to a specific URL path.+layout.svelte: The main layout component for the entire application.+page.svelte: The Svelte component for the main landing page of SCRMBLD.api/: Contains server-side API endpoints.gameplay/: Handles submissions and retrieval of gameplay data. Contains types likegameplay.type.ts.generate/: Likely related to word generation or other data generation tasks.
play/: Contains the routes for the game playing interface.+page.svelte: The main page for playing the game.+page.server.ts: Server-side logic for the play page.
stats/:+page.svelte: The Svelte component for displaying game statistics.+page.server.ts: The server-side script that loads and processes data for the/statspage from the database.
app.d.ts: TypeScript declarations file, often used for global type definitions specific to the SvelteKit app.app.html: The main HTML template file for your application. SvelteKit injects the rendered page content and necessary scripts/styles here.
-
static/: This directory is for static assets that are served directly by the web server and are not processed by Vite (SvelteKit's build tool).favicon.svg: The favicon for the application.scrmbld-logo-web.png: The logo image used in the application (e.g., on the stats page).- Various other images (
facebook.png,scrmbld-card.jpg, etc.), word lists (5-letter-words.json,allwords.txt, etc.), and sound files (splitflap.mp3).
-
package.json: Defines project metadata, dependencies (like Svelte, SvelteKit, Vite), and scripts (e.g.,dev,build,preview,lint,format). -
svelte.config.js: Configuration file for SvelteKit, including adapters (e.g., for deploying to different platforms), preprocessors (likesvelte-preprocess), and other Svelte-specific settings. -
tsconfig.json: TypeScript configuration file, defining compiler options for the project. -
vite.config.ts: Configuration file for Vite, the build tool used by SvelteKit. Allows customization of the build process, plugins, etc. -
.gitignore: Specifies intentionally untracked files that Git should ignore. -
.npmrc: Configuration file for npm. -
.prettierignore,.prettierrc: Configuration files for Prettier, a code formatter. -
eslint.config.js: Configuration file for ESLint, a static code analysis tool for identifying problematic patterns found in JavaScript/TypeScript code. -
LICENSE: Contains the software license for the project. -
pnpm-lock.yaml: The lockfile generated by pnpm, ensuring consistent dependency installation.
This structure helps organize the codebase, separating concerns between UI components, routing logic, server-side operations, and static assets.
The src/routes/play/+page.svelte component is the heart of the SCRMBLD game, providing the main user interface for gameplay. It manages the game's state, handles user interactions, and orchestrates the overall game flow.
The component relies heavily on Svelte's reactive primitives for state management:
data($prop): Loaded from the corresponding+page.server.ts, this prop containswords, which is an object including theanswer(the 7-letter solution) andall(the 8 scrambled letters including theanswerand one extramixletter).todaysWord($derived): An object derived fromdata.words, containing the 8 letters for today's game.answer($derived): The 7-letter target word, extracted fromdata.words.answer.mixletters($state): An array of characters representing the initial 8 scrambled letters (theanswerplus one extra letter). This state is modified by hints.scrambled($state): The currently displayed 8 scrambled letters that the user can click to form their attempt. This is regenerated by theshuffle()function.attempt($state): A string representing the user's current 7-letter attempt to match theanswer.hintLetters($state): An array of characters representing letters that have been revealed as hints directly in theanswerslots. Initially empty.numHintsUsed($state): A number tracking how many hints the user has activated.times($state): An array storing timestamps, typically[startTime, endTime], to calculate the duration of the game.time($derived): The total time taken in seconds, calculated fromtimes.timeDisplay($derived): A formatted string (e.g., "MM:SS") representing thetimefor display.success($state): A boolean indicating whether the user has correctly guessed theanswer.gameplayID($state): A unique ID for the current game session, used for API interactions and local storage.transitioning($state): A boolean to manage UI state during transitions or animations (e.g. after a successful guess).showConfetti($state): A boolean that triggers a confetti animation upon successful completion of the game.
-
Word Fetching and Processing:
- On initialization, the component receives
data.wordsfrom its server-sideloadfunction. todaysWordis derived fromdata.words.all(the 8 letters).- The 7-letter
answeris extracted fromdata.words.answer. - The initial
mixlettersarray is populated with the 8 characters fromtodaysWord. These are the letters available for shuffling and for the user to pick from.
- On initialization, the component receives
-
User Input Handling:
- The user types their
attemptusing either their physical keyboard or the on-screenKeyboard.sveltecomponent. - Keystrokes update the
attemptstring. TheFlipText.sveltecomponent likely displays thisattemptwith an animation. - Users can also click on the
scrambledletters displayed on screen to add them to theirattempt. - Functionality for backspace/delete and clearing the attempt is also handled.
- The user types their
-
Shuffling Mechanism:
- The
shuffle()function is responsible for randomizing the display of the available letters. - It takes the current
answerand the remainingmixletters(which initially is just the 8th extra letter, but can be reduced by hints). - It combines these letters and shuffles them using a
randomNumberGenerator(likely imported from$lib/math.ts). - The result updates the
scrambledstate, which is then rendered to the user. This ensures the 8 letter choices are presented in a random order.
- The
-
Hint System (
applyHint()):- The
applyHint()function provides assistance to the player. - First Hint Type (Reducing
mixletters): Ifmixletters(the extra letters not part of theanswer) contains more than one letter (this scenario might occur if hints can remove decoy letters from a larger set, though typically for an 8-letter game with a 7-letter answer,mixlettersinitially holds the single extra letter), the hint might remove one of these extra letters, making it easier to identify the correct 7. - Second Hint Type (Revealing Answer Letters): If
mixletterscontains only the single correct extra letter, subsequent hints will start revealing letters of theanswerone by one. - Revealed answer letters are added to the
hintLettersstate, which would then likely cause those letters to be displayed as fixed in theattemptdisplay area. numHintsUsedis incremented with each hint.- The
shuffle()function is typically called after a hint to re-render the available letters.
- The
-
Timer Logic:
- When a new game starts (or resumes and is not yet completed),
times[0]is set to the current timestamp. - When the user successfully completes the word,
times[1]is set. - The
time(total seconds) is derived by calculating the difference betweentimes[1]andtimes[0]. timeDisplayis a derived state that formatstimeinto a human-readable string (e.g., "01:23").
- When a new game starts (or resumes and is not yet completed),
-
Win Condition:
- The
successstate is set totruewhen the user'sattemptstring exactly matches theanswerstring. - Upon
success, the timer stops, and aConfetticomponent (or similar effect) is triggered by settingshowConfettito true. - The UI likely transitions to a success screen showing the final time and share options.
- The
-
Local Storage:
- To allow users to resume a game in progress or retain their stats for the day:
- On game start or when significant state changes (e.g.,
attemptupdated, hint used, game won), relevant states likeattempt,times,success,hintLetters,numHintsUsed, andgameplayIDare saved tolocalStorage. - When the page loads, it attempts to load this saved state from
localStorage. If a saved game for the current day's word exists and is not yet completed, the game state is restored, allowing the user to continue.
- On game start or when significant state changes (e.g.,
- To allow users to resume a game in progress or retain their stats for the day:
-
API Interaction:
- On Game Start: When a new game session begins (or when a persisted
gameplayIDis not found in local storage for the current day's word), the component makes an asynchronous POST request to/api/gameplay. This request likely sends thewordanduser_uuidto create a new gameplay record in the database and receives a uniquegameplayIDin response. - On Success: When
successbecomes true, another asynchronous POST request is made to/api/gameplay/{gameplayID}/finish. This request updates the existing gameplay record with theended_attimestamp, calculatedtimetaken (from thetimesarray sent by the client), and thejsonfield storing the detailedtimesarray.
- On Game Start: When a new game session begins (or when a persisted
-
Sharing Functionality:
- After successfully completing the game, share buttons (e.g., for social media or copying to clipboard) are displayed.
- A
shareTextstring is constructed, typically including the game name (SCRMBLD), the date or word number, the user's time, and the number of hints used, formatted in an emoji-based or plain text grid to represent their game.
This component orchestrates these features to create a complete and interactive word puzzle experience.
The src/lib/ directory houses reusable Svelte components that form the building blocks of the SCRMBLD user interface.
- Purpose: This component is responsible for displaying text (primarily the user's attempt and hints) with an engaging character-flipping animation, reminiscent of old-style airport or train station display boards. Each character individually animates to its new state.
- Key Features/Props:
word(String): The text string to be displayed and animated.alphabet(Array): The set of characters that the component cycles through during animation for each letter slot.duration(Number): Controls the speed of the flip animation for each character.minLength(Number): Ensures the display has a minimum number of character slots, padding with blanks if necessary.success(Boolean) /error(Boolean): Boolean props that apply distinct styling to the text, typically to indicate correct or incorrect attempts.usedLetters(Set): A set of indices indicating which character slots should have a "used" or "locked-in" visual appearance, often used for showing revealed hint letters.selectionStart/selectionEnd(Number): Props to visually indicate a selected range of letters.
- Purpose: Renders a virtual QWERTY keyboard on the screen, allowing users to input their attempts, especially on touch devices.
- Key Features/Props:
onclick(Function): A callback prop that is invoked when any key on the virtual keyboard is pressed. It typically emits the character of the key (e.g., "Q", "A") or a special command (e.g., "Backspace", "Clear").- Layout: Provides a standard QWERTY layout.
- Special Keys: Includes functional keys like "CLEAR" (to erase the entire attempt) and "Backspace" (to delete the last character).
- Feedback: May incorporate visual feedback on key press, like a ripple effect (as suggested by
ripple.tsin the project).
- Purpose: A highly configurable component used to display floating informational popovers or tooltips that are anchored to a specific reference HTML element. This could be used for hints, definitions, or other contextual information.
- Key Features/Props:
- Positioning Engine: Leverages the powerful
@floating-ui/domlibrary for robust positioning, ensuring the popover remains visible and well-placed even with screen-edge constraints (it can flip or shift as needed). refElement(HTMLElement): The HTML element to which the popover is attached or anchored.opened(Boolean, bindable): Controls the visibility state of the popover. Being bindable allows two-way data binding for easy state management.- Trigger Options:
openOnHover(Boolean): Opens the popover when the mouse hovers over therefElement.openOnClick(Boolean): Opens the popover when therefElementis clicked.openOnFocus(Boolean): Opens the popover when therefElementreceives focus.
arrow(Boolean): If true, displays a small arrow pointing from the popover to itsrefElement.- Content: The content of the popover is injected using a Svelte
{#snippet children}block, allowing for flexible and rich content. - Closing Behavior: Configurable options like
closeOnOutsideClick,closeOnEscapeKeydetermine how the popover can be dismissed.
- Positioning Engine: Leverages the powerful
Several TypeScript modules in src/lib/ provide utility functions and Svelte actions to support the frontend logic.
- Purpose: Provides mathematical utility functions, particularly for generating seeded random numbers.
- Key Functions:
seededRandom(seed: number): This function returns a new pseudo-random number generator (PRNG) that uses the providedseed. This means that for the same initial seed, it will always produce the same sequence of random numbers, ensuring deterministic behavior where needed (e.g., consistently shuffling words or generating predictable sequences for specific days).randomNumberGenerator(): This function likely returns an instance of a seeded random number generator. It might use a default seed (e.g., derived from the current date for daily consistency) or allow a seed to be passed. This generator is then used elsewhere in the application, such as in theshuffle()mechanism ofplay/+page.svelte, to ensure that the scrambling of letters is random but consistent for a given day if seeded appropriately.
- Purpose: This module likely exports a Svelte action, used with the
use:rippledirective on an HTML element. - Functionality: It adds a Material Design-like ripple effect to elements upon user interaction (typically a click or tap). This enhances UI feedback by providing a visual confirmation that an interaction has occurred. It's used on elements like the virtual keyboard keys.
- Purpose: This module, despite the typo
tootltip.tsin its filename, is expected to provide a Svelte action for displaying tooltips. It's used with theuse:tooltipdirective. - Functionality: When applied to an element, this action will show a specified tooltip message when the user hovers over or focuses on the element. It leverages the
@floating-ui/domlibrary (similar toPopover.svelte) for intelligent positioning of the tooltip relative to the target element, ensuring it remains visible and well-placed.
The backend logic for SCRMBLD handles serving daily words, tracking game progress, and aggregating statistics. It primarily uses SvelteKit's server routes and Cloudflare D1 for database interactions.
- Purpose: The
getDailyWord()function within this service is responsible for determining and providing the game words for yesterday, today, and tomorrow. This allows the game to show previous results and potentially pre-load upcoming words. - Word Source: It relies on
static/wordlist.json, which contains afirstDaytimestamp (indicating the start date of the word list) and alistof word pairs. Each pair typically includes the 7-letter solution word and the 8th extra letter. - Data Preparation: For each of the three days (yesterday, today, tomorrow), it calculates the correct index into the
wordlist.jsonbased on the current date and thefirstDaytimestamp. It then prepares an object for the frontend, including:day: The specific date/timestamp for that word.word: The 7-letter solution (e.g., "EXAMPLE").extraLetters: An array containing the 8th letter that, combined with the solution, forms the 8 scrambled letters for the game (e.g.,['Q']). The frontend then combineswordandextraLettersto form the initialmixletters.
- Overall Purpose: This API is crucial for tracking player game sessions and statistics. It uses Cloudflare D1 as its database, defined by
src/lib/server/schema.sql. The structure of gameplay data is defined by theGamePlayinterface, likely found insrc/routes/api/gameplay/gameplay.type.ts. POST /api/gameplay:- Trigger: Called automatically when a player initiates a new game on the frontend (e.g., on load of
play/+page.svelteif no active game for the day is found in local storage). - Functionality: Records the beginning of a new game session.
- Data Stored:
user_uuid: A unique identifier for the user (often retrieved from a cookie or generated if new).uuid: A newly generated unique ID (UUID v4) for this specific gameplay session.wordandday: The specific word and corresponding day timestamp for the game being played.started_at: Timestamp marking when the game started.- Anonymized request details: May include IP-derived location (city, country, region, timezone, latitude, longitude) and User Agent information (browser, OS, device) for general analytics, as defined in the
gameplaytable schema.
- Trigger: Called automatically when a player initiates a new game on the frontend (e.g., on load of
POST /api/gameplay/{gameplay_id}/finish:- Trigger: Called from the frontend when a player successfully guesses the word. The
gameplay_idis the UUID generated by the initialPOST /api/gameplaycall. - Functionality: Updates the existing gameplay record to mark it as completed and store performance metrics.
- Data Updated:
ended_at: Timestamp marking when the game was successfully completed.time: The total active time taken by the player in milliseconds. This is often calculated from atimesarray sent by the client, which records segments of active play (pauses might be excluded).json: This field in the database stores the detailedtimesarray (segments of play) and potentially other game-specific details likenumHintsUsedand the finalattempt.
- Trigger: Called from the frontend when a player successfully guesses the word. The
- Database (
gameplaytable):- The
gameplaytable is the core storage for all game attempts. Key columns include:id: Auto-incrementing primary key.uuid: Unique identifier for the gameplay session.word: The target word for the game.day: Integer timestamp for the day of the word.user_uuid: Unique identifier for the player.started_at,ended_at: Timestamps for game start and end.time: Numeric value for total time taken in milliseconds for successful attempts.json: A TEXT field to store JSON data, such as the array of play/pause timestamps, number of hints, etc.- IP-derived location fields and User Agent fields for analytics.
- The
src/routes/stats/+page.server.ts:- Purpose: This server-side script is responsible for fetching and processing data to display game statistics.
- Functionality: It queries the Cloudflare D1
gameplaytable, typically retrieving records for the last 7 days to show recent trends. - Data Aggregation: The script groups the raw gameplay data by
day. For each day, it calculates:- The
wordfor that day. numAttempts: The total number of recorded gameplays (both completed and incomplete, or just completed depending on query logic).numCorrect: The number of successful gameplays.average: The average completiontimefor all successful plays on that day.
- The
- Return Value: It returns an object containing the processed
statsarray, which is then passed as a prop to the+page.sveltecomponent.
src/routes/stats/+page.svelte:- Purpose: This Svelte component displays the aggregated game statistics provided by its server-side counterpart.
- Functionality: It iterates through the
statsarray and renders the information for each day, typically including theword, date (day), totalnumAttempts, numbernumCorrect, and the calculatedaveragetime (formatted for display, e.g., MM:SS). - SEO: The page includes a
<meta name="robots" content="noindex" />tag, indicating to search engines not to index this statistics page.
The selection and generation of words for SCRMBLD is a critical process, ensuring fairness and a continuous supply of puzzles. This involves a canonical word list and developer-facing scripts to manage it.
- Role: This file is the source of truth for the daily words used in the live game. It provides a predetermined sequence of words, ensuring all players receive the same puzzle each day.
- Structure:
firstDay(String/Timestamp): A timestamp string (e.g., "2023-01-01T00:00:00.000Z") representing the date of the first word in thelist. This is used as a reference point to calculate which word corresponds to the current date.list(Array): An array of word pairs. Each element in the array is itself an array containing two strings:["SOLUTION_WORD", "EXTRA_LETTER"]- Example:
["EXAMPLE", "Q"]means "EXAMPLE" is the 7-letter solution, and "Q" is the 8th letter that will be scrambled along with the solution letters.
These scripts are developer tools designed for generating and updating the static/wordlist.json file. They are not used by players during normal gameplay.
-
GET /api/generate(for 7-letter words):- Purpose: This endpoint is used to create or extend
static/wordlist.jsonwith new 7-letter words and their corresponding 8th extra letter. - Inputs:
static/wordlist.txt: A curated list of valid 7-letter words, one per line, that are candidates for solutions.static/allwords.txt: A more comprehensive list of 7-letter words, used for checking potential collisions (see below).- Existing
static/wordlist.json: The script reads the current word list to maintain the canonical order of words already published or scheduled, appending new words after the existing ones.
- Core Logic:
- Extra Letter Selection: For each 7-letter solution word from
wordlist.txt, the script intelligently selects an appropriate 8th "extra letter." - Collision Avoidance: This is a crucial step for fairness. The 8 chosen letters (7 solution + 1 extra) should not be easily re-arrangeable to form other valid 7-letter words if a different letter from the 8 is omitted. To achieve this:
- A "word hash" is created for the 8-letter combination (e.g., by sorting its letters alphabetically).
- The script then checks if removing any one of the 8 letters and checking the remaining 7 against
allwords.txtwould result in a valid word other than the intended solution. The goal is to pick an 8th letter that minimizes such collisions, ensuring there's primarily one intended 7-letter solution from the 8 scrambled letters.
- Deterministic Shuffling for Future Words: While the order of words already in
wordlist.jsonis preserved, any new words added to the list are shuffled deterministically. This ensures that if the script is run multiple times with the same input word lists, the sequence of newly added words will be the same, providing a varied but predictable schedule.
- Extra Letter Selection: For each 7-letter solution word from
- Output: The script outputs a JSON structure (matching the format of
static/wordlist.json) which can then be saved to update the canonical word list.
- Purpose: This endpoint is used to create or extend
-
GET /api/generate/5(for 5-letter words):- Purpose: Similar to the 7-letter generator, this endpoint is designed to generate a list of 5-letter words, but in this case, with two extra letters (forming a set of 7 letters to be scrambled).
- Input: It primarily uses
static/5-letter-words.txtas its source of 5-letter solution words. - Note: This functionality might be for a different game mode (e.g., a 5-letter SCRMBLD variant) or an experimental feature, as the main game focuses on 7-letter words from 8. The collision avoidance logic would be adapted for the 5-from-7 scenario.
The project uses standard tools for building, configuring, and maintaining the codebase.
-
package.json: This file is central to managing the project.- Dependencies: It lists all project dependencies, such as Svelte, SvelteKit, Vite, Cloudflare D1 client/types for database interaction, and various Svelte utility libraries.
- Scripts: It defines command-line scripts for common development tasks:
dev: Starts the development server with hot module reloading.build: Builds the application for production.preview: Serves the production build locally for testing.lint: Runs ESLint to check for code quality and style issues.format: Runs Prettier to automatically format the codebase.
-
SvelteKit/Vite Configuration:
svelte.config.js: The main configuration file for SvelteKit. It specifies:- Preprocessors: Such as
svelte-preprocessfor enabling PostCSS, SCSS, or TypeScript within Svelte components. - Adapter: Configures how SvelteKit builds the output for specific deployment platforms (e.g.,
adapter-cloudflarefor Cloudflare Pages/Workers).
- Preprocessors: Such as
vite.config.ts: Provides configuration options for Vite, the underlying build tool. This includes the SvelteKit Vite plugin (@sveltejs/kit/vite) and any other Vite-specific customizations or plugins.
-
TypeScript Configuration (
tsconfig.json):- Defines compiler options for TypeScript, such as target JavaScript version, module resolution strategy, type checking strictness, and paths for module aliases (e.g.,
$lib).
- Defines compiler options for TypeScript, such as target JavaScript version, module resolution strategy, type checking strictness, and paths for module aliases (e.g.,
-
Linting and Formatting:
eslint.config.js: Contains the configuration for ESLint, a tool for identifying and reporting on patterns in JavaScript and TypeScript code, helping to maintain code quality and prevent errors..prettierrcand.prettierignore: These files configure Prettier, an opinionated code formatter..prettierrcdefines the formatting rules (e.g., tab width, semi-colons), and.prettierignorelists files and directories that Prettier should not attempt to format.
This section includes ideas and plans that were part of the original project scope or could be considered for future development.
Add every user's 'finish' time in a database table
Every X minutes, re-caculate a histogram table for each day's word.
The histogram table should look something like this:
day | percentile | min | max | count
--------------------------------------------------
123412341234 | 1 | 1000 | 100000 | 0
123412341234 | 2 | 500 | 1000 | 2
123412341234 | 3 | 100 | 500 | 5
...
123412341234 | 100 | 0 | 20 | 1
The day column shows the epoch timestamp in UTC of the days word that was played
The percentile shows the 1-100 percentile 'score' based on the finish time
The min in the minimum time in ms the user must get to be in this percentile
The max in the maximum time in ms the user must get to be in this percentile
The count is the number of user times that fall into this percentile
To generate this table, use the 'finish' time table to retrieve all the scores/times for all users for that day's word
Order the table by finish time.
Loop through every "finish time" entry.
Calculate the percentile of each entry by counting the number of values below this entry (simply use the index) and divide by the total number of entries. Multiply by 100.
Take this percentile and increase the count in the histogram table for that percentile.
Adjust the min/max times for each percentile based on each entry that falls in that percentile.
For example if an entry is in the 1st percentile and has a time of 10s, the min/max should be adjusted to ensure 10s fits in that range.
Then whenever a different user finishes the game, the server can simply compare their time to the min/max of the histogram table to get the user's percentile