Skip to content

takempf/subak-game

Repository files navigation

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.

About

A fruit merging puzzle game, built with Svelte and Rapier

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors