diff --git a/assets/js/pages/games/whittler.js b/assets/js/pages/games/whittler.js new file mode 100644 index 0000000..1ed245f --- /dev/null +++ b/assets/js/pages/games/whittler.js @@ -0,0 +1,158 @@ +/** + * Whittler Game High Scores + * Fetches and displays high scores from the ritual.sh high score API + */ + +class WhittlerHighScores { + constructor() { + this.apiUrl = "https://api.ritual.sh/highscore"; + this.gameId = "whittling-clicker"; + this.container = document.getElementById("whittler-highscores"); + this.maxScores = 10; + + if (this.container) { + this.init(); + } + } + + async init() { + await this.loadHighScores(); + } + + /** + * Fetch high scores from the API + */ + async loadHighScores() { + try { + this.showLoading(); + + const response = await fetch( + `${this.apiUrl}/leaderboard?game_id=${this.gameId}&limit=${this.maxScores}`, + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (data.success && data.scores) { + this.renderScores(data.scores); + } else { + throw new Error(data.error || "Failed to load high scores"); + } + } catch (error) { + console.error("Error loading high scores:", error); + this.showError(); + } + } + + /** + * Render high scores to the DOM + */ + renderScores(scores) { + if (!scores || scores.length === 0) { + this.container.innerHTML = ` +
No high scores yet. Be the first!
+Loading high scores...
+Failed to load high scores.
+ ++ Click the log pile, automatically whittle stuff, buy + unlocks. +
++ A game should take approximately 20 minutes. Many of the unlocks + synergise with each other, see which combinations work best early + and late game! +
++ Note: This is a very early version of Whittler, it + will be buggy and the balance will suck. There will be more + releases. If you have feedback, bug reports, or suggestions of new + modifiers or features just drop me an + email! +
+
-
-
- This application requires an Internet connection to run for the first time.
-Press the button below to try reloading:
- - - - diff --git a/static/games/clicker/index.pck b/static/games/clicker/index.pck deleted file mode 100644 index b9c378a..0000000 Binary files a/static/games/clicker/index.pck and /dev/null differ diff --git a/static/games/clicker/index.png b/static/games/clicker/index.png deleted file mode 100644 index 5b27435..0000000 Binary files a/static/games/clicker/index.png and /dev/null differ diff --git a/static/games/clicker/index.service.worker.js b/static/games/clicker/index.service.worker.js deleted file mode 100644 index a4215c8..0000000 --- a/static/games/clicker/index.service.worker.js +++ /dev/null @@ -1,166 +0,0 @@ -// This service worker is required to expose an exported Godot project as a -// Progressive Web App. It provides an offline fallback page telling the user -// that they need an Internet connection to run the project if desired. -// Incrementing CACHE_VERSION will kick off the install event and force -// previously cached resources to be updated from the network. -/** @type {string} */ -const CACHE_VERSION = '1769693384|4844716121'; -/** @type {string} */ -const CACHE_PREFIX = 'Clicker-sw-cache-'; -const CACHE_NAME = CACHE_PREFIX + CACHE_VERSION; -/** @type {string} */ -const OFFLINE_URL = 'index.offline.html'; -/** @type {boolean} */ -const ENSURE_CROSSORIGIN_ISOLATION_HEADERS = true; -// Files that will be cached on load. -/** @type {string[]} */ -const CACHED_FILES = ["index.html","index.js","index.offline.html","index.icon.png","index.apple-touch-icon.png","index.audio.worklet.js","index.audio.position.worklet.js"]; -// Files that we might not want the user to preload, and will only be cached on first load. -/** @type {string[]} */ -const CACHEABLE_FILES = ["index.wasm","index.pck"]; -const FULL_CACHE = CACHED_FILES.concat(CACHEABLE_FILES); - -self.addEventListener('install', (event) => { - event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(CACHED_FILES))); -}); - -self.addEventListener('activate', (event) => { - event.waitUntil(caches.keys().then( - function (keys) { - // Remove old caches. - return Promise.all(keys.filter((key) => key.startsWith(CACHE_PREFIX) && key !== CACHE_NAME).map((key) => caches.delete(key))); - } - ).then(function () { - // Enable navigation preload if available. - return ('navigationPreload' in self.registration) ? self.registration.navigationPreload.enable() : Promise.resolve(); - })); -}); - -/** - * Ensures that the response has the correct COEP/COOP headers - * @param {Response} response - * @returns {Response} - */ -function ensureCrossOriginIsolationHeaders(response) { - if (response.headers.get('Cross-Origin-Embedder-Policy') === 'require-corp' - && response.headers.get('Cross-Origin-Opener-Policy') === 'same-origin') { - return response; - } - - const crossOriginIsolatedHeaders = new Headers(response.headers); - crossOriginIsolatedHeaders.set('Cross-Origin-Embedder-Policy', 'require-corp'); - crossOriginIsolatedHeaders.set('Cross-Origin-Opener-Policy', 'same-origin'); - const newResponse = new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers: crossOriginIsolatedHeaders, - }); - - return newResponse; -} - -/** - * Calls fetch and cache the result if it is cacheable - * @param {FetchEvent} event - * @param {Cache} cache - * @param {boolean} isCacheable - * @returns {Response} - */ -async function fetchAndCache(event, cache, isCacheable) { - // Use the preloaded response, if it's there - /** @type { Response } */ - let response = await event.preloadResponse; - if (response == null) { - // Or, go over network. - response = await self.fetch(event.request); - } - - if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) { - response = ensureCrossOriginIsolationHeaders(response); - } - - if (isCacheable) { - // And update the cache - cache.put(event.request, response.clone()); - } - - return response; -} - -self.addEventListener( - 'fetch', - /** - * Triggered on fetch - * @param {FetchEvent} event - */ - (event) => { - const isNavigate = event.request.mode === 'navigate'; - const url = event.request.url || ''; - const referrer = event.request.referrer || ''; - const base = referrer.slice(0, referrer.lastIndexOf('/') + 1); - const local = url.startsWith(base) ? url.replace(base, '') : ''; - const isCacheable = FULL_CACHE.some((v) => v === local) || (base === referrer && base.endsWith(CACHED_FILES[0])); - if (isNavigate || isCacheable) { - event.respondWith((async () => { - // Try to use cache first - const cache = await caches.open(CACHE_NAME); - if (isNavigate) { - // Check if we have full cache during HTML page request. - /** @type {Response[]} */ - const fullCache = await Promise.all(FULL_CACHE.map((name) => cache.match(name))); - const missing = fullCache.some((v) => v === undefined); - if (missing) { - try { - // Try network if some cached file is missing (so we can display offline page in case). - const response = await fetchAndCache(event, cache, isCacheable); - return response; - } catch (e) { - // And return the hopefully always cached offline page in case of network failure. - console.error('Network error: ', e); // eslint-disable-line no-console - return caches.match(OFFLINE_URL); - } - } - } - let cached = await cache.match(event.request); - if (cached != null) { - if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) { - cached = ensureCrossOriginIsolationHeaders(cached); - } - return cached; - } - // Try network if don't have it in cache. - const response = await fetchAndCache(event, cache, isCacheable); - return response; - })()); - } else if (ENSURE_CROSSORIGIN_ISOLATION_HEADERS) { - event.respondWith((async () => { - let response = await fetch(event.request); - response = ensureCrossOriginIsolationHeaders(response); - return response; - })()); - } - } -); - -self.addEventListener('message', (event) => { - // No cross origin - if (event.origin !== self.origin) { - return; - } - const id = event.source.id || ''; - const msg = event.data || ''; - // Ensure it's one of our clients. - self.clients.get(id).then(function (client) { - if (!client) { - return; // Not a valid client. - } - if (msg === 'claim') { - self.skipWaiting().then(() => self.clients.claim()); - } else if (msg === 'clear') { - caches.delete(CACHE_NAME); - } else if (msg === 'update') { - self.skipWaiting().then(() => self.clients.claim()).then(() => self.clients.matchAll()).then((all) => all.forEach((c) => c.navigate(c.url))); - } - }); -}); - diff --git a/static/games/clicker/index.wasm b/static/games/clicker/index.wasm deleted file mode 100644 index 55d5f55..0000000 Binary files a/static/games/clicker/index.wasm and /dev/null differ