Back to Pickle Rustlers
SOURCE / PINNED RELEASE

Made of little things.

Pickle Rustlers

Release
039e51f55e34…
Author-recorded commit
40608586a8a1…
License
LICENSE
Author’s source reference
nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-bad44e7ab35

Archive hash verified: f38e99afa0992052…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/main.ts
import { STORAGE_DOMAIN, storage } from '@napplet/sdk';
import { runtimeHasDomain } from './domain-availability.js';
import { Game, type Progress } from './game/game.js';
import { setSoundEnabled } from './game/sound.js';
import { startSettings } from './napplet-settings.js';
import { createScoreShare } from './share/share.js';
import './styles.css';

// Colors are app-owned: the desert, the pickles, the onions and the wanted-poster HUD
// keep their palette in every host theme (see docs/brief.md). Matching the host is
// an app choice, not a NAP requirement, so no theme handler is installed.
const PROGRESS_KEY = 'progress-v1';

async function loadProgress(): Promise<Progress> {
  const empty = { best: 0, wins: 0 };
  if (!runtimeHasDomain(STORAGE_DOMAIN)) return empty;
  const raw = await storage.getItem(PROGRESS_KEY);
  if (!raw) return empty;
  try {
    const parsed = JSON.parse(raw) as Partial<Progress>;
    const count = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) && v > 0 ? Math.floor(v) : 0);
    return { best: count(parsed.best), wins: count(parsed.wins) };
  } catch {
    return empty;
  }
}

async function saveProgress(progress: Progress): Promise<void> {
  // Without storage, progress lasts only for this session.
  if (!runtimeHasDomain(STORAGE_DOMAIN)) return;
  await storage.setItem(PROGRESS_KEY, JSON.stringify(progress));
}

const canvas = document.querySelector<HTMLCanvasElement>('#scene');
if (!canvas) throw new Error('Missing #scene canvas');

let game: Game | null = null;
try {
  // Score-card posting needs identity, upload and outbox; otherwise it stays hidden.
  game = new Game({ canvas, loadProgress, saveProgress, share: createScoreShare() });
} catch (error) {
  // WebGL can be unavailable (old hardware, disabled GPU). Say so plainly.
  document.getElementById('noGl')?.removeAttribute('hidden');
  document.getElementById('posterWrap')?.setAttribute('hidden', '');
  console.error(error);
}

window.addEventListener('pagehide', (event) => {
  if (!event.persisted) game?.dispose();
});

// Optional settings (sound and screen shake). Without the config domain the defaults stay on.
if (runtimeHasDomain('config')) {
  startSettings((values) => {
    setSoundEnabled(values.sound !== false);
    if (game) game.shakeEnabled = values.screenShake !== false;
  });
}