subak-game Svelte Themes

Subak Game

A fruit merging puzzle game, built with Svelte and Rapier

Subak Game šŸ‰

Subak Game with a pile of fruit

A browser-based fruit-merging puzzle game inspired by Suika Game. Drop fruits into a container — when two identical fruits touch, they merge into the next fruit in the evolution chain. Combine your way up through 11 fruits (blueberry → grape → lemon → orange → apple → dragonfruit → pear → peach → pineapple → honeydew → watermelon) and chase the highest score.

The original game, "Merge Big Watermelon" (åˆęˆå¤§č„æē“œ), was created by Meadow Science (ē±³å…œē§‘ęŠ€). This project was built as a fun memento of a team off-site where I accidentally got my entire team hooked on Suika Game. "Subak" (ģˆ˜ė°•) is Korean for watermelon.

Play it live → subak.kempf.dev


Gameplay

Subak Game gameplay screenshot showing many fruit in the gameplay area

  • Drop a fruit anywhere along the top of the container.
  • Merge — when two identical fruits collide, they fuse into the next-larger fruit and award points.
  • Game over when any fruit breaches the danger line at the top.
  • Scores are saved locally (IndexedDB) and optionally submitted to a global leaderboard.

Architecture

The project serves two roles:

Mode Description
Standalone app A full SvelteKit single-page app deployed as a static site via @sveltejs/adapter-static.
Embeddable library Published as an npm package exporting a SubakGame Svelte component (import { SubakGame } from 'subak-game').

Key modules

src/
ā”œā”€ā”€ lib/
│   ā”œā”€ā”€ api/                  # LeaderboardClient — reactive Svelte 5 class managing
│   │                         #   session tokens, score submission, and global score fetching
│   ā”œā”€ā”€ components/           # UI layer (Game, Leaderboard, modals, merge effects, etc.)
│   ā”œā”€ā”€ game/                 # Physics-layer classes
│   │   ā”œā”€ā”€ Fruit.ts          #   Fruit rigid-body wrapper (Rapier colliders, merging)
│   │   ā”œā”€ā”€ Boundary.ts       #   Wall / floor collider creation
│   │   └── AudioManager.svelte.ts  #   Sound effect management via Howler
│   ā”œā”€ā”€ hooks/                # Reactive utilities (useBoundingRect, useCursorPosition)
│   ā”œā”€ā”€ stores/
│   │   ā”œā”€ā”€ game.svelte.ts    #   Core GameState class — physics loop, collision detection,
│   │   │                     #   score tracking, fruit spawning, game-over logic
│   │   ā”œā”€ā”€ db.ts             #   Local high-score persistence (Dexie / IndexedDB)
│   │   └── telemetry.svelte.ts  #   Session telemetry & anti-cheat payload builder
│   ā”œā”€ā”€ icons/ & svg/         # Inline SVG fruit sprites and UI icons
│   └── types/                # Shared TypeScript interfaces
ā”œā”€ā”€ routes/                   # SvelteKit page entry point
└── utils/                    # Web analytics (PostHog) initializer

Tech Stack

Layer Tool Why
UI Svelte 5 Fine-grained reactivity via $state runes, minimal runtime overhead
Physics Rapier (rapier2d-compat) WASM-powered 2D rigid-body simulation with deterministic collision events
Audio Howler Cross-browser sound playback with volume and pitch control
Local storage Dexie Ergonomic IndexedDB wrapper for persisting local high scores
Screenshots modern-screenshot DOM-to-image capture for sharing game-over screens
Telemetry PostHog Optional web analytics
Build Vite + SvelteKit Fast HMR, SSG via adapter-static, library mode via svelte-package
Linting Biome Formatting and linting in a single tool
Testing Vitest (browser mode) + Playwright Real-browser test execution with @testing-library/svelte
Type checking TypeScript + svelte-check Full strict-mode type safety across .ts and .svelte files

Prerequisites

  • Node.js ≄ 18
  • npm ≄ 9

Getting Started

1. Clone and install

git clone https://github.com/Fauntleroy/subak-game.git
cd subak-game
npm install

2. Configure environment

cp .env.example .env
Variable Purpose Default
VITE_APP_VERSION Injected build version $npm_package_version
VITE_POSTHOG_TOKEN PostHog analytics token (optional) —
PUBLIC_SHARED_CLIENT_SALT Shared salt for anti-cheat hash "public_secret_hash_salt"
PUBLIC_LEADERBOARD_URL Leaderboard API base URL http://localhost:3001

Leaderboard is optional. The game runs fully offline — the leaderboard client gracefully handles missing or unavailable servers.

3. Run the dev server

npm run dev

Open http://localhost:4032 (port 4032 = PLU code of ģˆ˜ė°• šŸ‰).


Scripts

Command Description
npm run dev Start Vite dev server with HMR
npm run build Production build → build/ (static site) + dist/ (library package)
npm run preview Preview the production build locally
npm run check Run svelte-check for type errors
npm run lint Lint with Biome
npm run format Auto-format with Biome
npm run test Run Vitest (browser mode, headless Chromium)
npm run test:watch Run Vitest in watch mode
npm run validate Full CI gate — format → lint → test → check

Testing

Tests run in Vitest browser mode using Playwright (headless Chromium) for real DOM and browser API access.

npm run test            # single run
npm run test:watch      # watch mode

Test files live alongside their source code in __tests__/ directories, covering components, game engine classes, stores, and hooks.


Building & Deployment

Static site (GitHub Pages)

npm run build

The SvelteKit static adapter outputs to build/. The docs/ directory contains a pre-built snapshot served via GitHub Pages at subak.kempf.dev.

Library package

npm run prepack

Outputs the publishable Svelte component library to dist/. Consumers import the game as:

<script>
  import { SubakGame } from 'subak-game';
</script>

<SubakGame />

Leaderboard Server

The game connects to an optional companion leaderboard API (subak-leaderboard) for global score tracking. Set PUBLIC_LEADERBOARD_URL in .env to point to a running instance. See the leaderboard repo for setup instructions.


Project Structure

subak-game/
ā”œā”€ā”€ src/
│   ā”œā”€ā”€ lib/              # Core library — components, game engine, stores, hooks
│   ā”œā”€ā”€ routes/           # SvelteKit app entry point
│   └── utils/            # Web analytics setup
ā”œā”€ā”€ static/               # Static assets (images, sounds, favicon)
ā”œā”€ā”€ design/               # Source design files (Affinity Designer)
ā”œā”€ā”€ docs/                 # Pre-built static site for GitHub Pages
ā”œā”€ā”€ dist/                 # Compiled library package output
ā”œā”€ā”€ biome.json            # Biome linter/formatter config
ā”œā”€ā”€ svelte.config.js      # SvelteKit config (static adapter)
ā”œā”€ā”€ vite.config.ts        # Vite config (dev server, build)
ā”œā”€ā”€ vitest.config.ts      # Vitest config (browser mode, Playwright)
└── tsconfig.json         # TypeScript config

Contributing

Contributions are welcome! If you have ideas for improvements, new features, or bug fixes, feel free to open an issue or submit a pull request.


License

This project is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0). See LICENSE.md for details.

Top categories

Loading Svelte Themes