78 lines
5.1 KiB
Markdown
78 lines
5.1 KiB
Markdown
# Idle Clicker
|
||
|
||
A themeable idle game inspired by *Universal Paperclips*. Make units by hand, sell them, automate production, buy upgrades. Runs in any browser (phone, tablet, desktop); progress is saved in cookies and can be exported/imported as a JSON or TXT file. There is **one game**: themes (paperclips, witch potions, alien minerals, leaf-piling insects) are only skins, so switching theme keeps every amount.
|
||
|
||
Zero runtime dependencies: a small Node server (`http` module only) plus a vanilla JS frontend (ES modules).
|
||
|
||
## Run it
|
||
|
||
```bash
|
||
docker compose up -d --build # http://localhost:8080 (GAME_PORT=9000 to change the port)
|
||
```
|
||
|
||
The container is a single image running as the unprivileged `node` user, with a read-only filesystem, no capabilities and `no-new-privileges`.
|
||
|
||
Without Docker: `npm start` (Node 22+). Tests: `npm test`.
|
||
|
||
## How the project is organised
|
||
|
||
```
|
||
server/ static file server + /api/themes and /api/locales (auto-discovery)
|
||
public/
|
||
index.html page skeleton (texts come from i18n via data-i18n attributes)
|
||
css/ base (tokens) · layout (responsive grid) · components
|
||
data/ GAME CONTENT, theme-independent: rules, generators, upgrades (ids + numbers)
|
||
locales/ core UI texts, one file per language (en, fr)
|
||
themes/<id>/ ONE FOLDER PER THEME
|
||
theme.json order + (later) image paths
|
||
theme.css colours/fonts, as CSS variables
|
||
locales/<lang>.json the theme's vocabulary: names of resources, generators, upgrades
|
||
js/
|
||
engine/ game rules, pure JS, no DOM (also used by the tests)
|
||
services/ cookies · storage · save import/export · i18n · formatting · data loading
|
||
ui/ DOM helpers · main view · settings dialog · toasts
|
||
main.js wires everything together
|
||
tests/ node:test suites (engine, save import/export, server, content completeness)
|
||
```
|
||
|
||
The key idea: **mechanics are shared, themes are skins.** `public/data/` defines generators and upgrades by id (`gen1`, `click_1`, …) with their numbers; a theme only gives those ids a name, a colour palette and, later, images. Rebalancing the game is a change in `public/data/`, not in each theme.
|
||
|
||
## Add a theme
|
||
|
||
1. Copy `public/themes/paperclip/` to `public/themes/<your-id>/` (lowercase letters, digits, dashes).
|
||
2. Edit `theme.css` (palette, font, radius), `theme.json` (`order`), and the texts in `locales/*.json`.
|
||
3. Reload. The server discovers the folder; no code change and no rebuild list. (With Docker, rebuild the image or mount the folder.)
|
||
|
||
`npm test` fails if a theme misses a name for any resource, generator or upgrade, or lacks a language the game supports.
|
||
|
||
## Add a language
|
||
|
||
1. Copy `public/locales/en.json` to `public/locales/<code>.json` and translate it (set `meta.name` to the language's own name).
|
||
2. Add `public/themes/<id>/locales/<code>.json` to every theme.
|
||
|
||
Missing keys fall back to English, then to the raw key. Strings accept `{param}` and `{@other.key}` (embeds another entry, which is how core texts use the theme's vocabulary, e.g. `{@theme.res.material}`).
|
||
|
||
## Images
|
||
|
||
Every icon is currently a grey square. To use real images, put files in the theme folder and list them in its `theme.json`:
|
||
|
||
```json
|
||
{ "order": 1, "assets": { "gen.gen1": "img/autoclipper.svg", "res.units": "img/clip.svg", "upg.click_1": "img/bender.svg" } }
|
||
```
|
||
|
||
Keys: `res.units|funds|material`, `gen.<id>`, `upg.<id>`. Anything not listed keeps the placeholder.
|
||
|
||
## Saves
|
||
|
||
- One shared game for all themes, stored in cookies (`ic_game_*`, chunked and base64-encoded because a cookie holds ~4 KB), plus one preferences cookie (theme + language). Saves from the first version (one per theme) are merged automatically: the most advanced one is kept.
|
||
- **Save & settings** exports the save as `.json` or `.txt` (download or copy/paste) and imports either (file picker or pasted text). The file does not depend on the theme. Imports are validated and sanitised: unknown ids are dropped, numbers clamped.
|
||
- Progress continues while the game is closed: up to 8 h, at 50 % efficiency (`public/data/rules.json`).
|
||
- Cookies are sent with every request to the server. A save is under 2 KB, far below header limits, but it is the reason to keep saves compact.
|
||
|
||
## Tuning the game
|
||
|
||
Everything numeric is in `public/data/`: `rules.json` (market, raw material, marketing, offline, autosave), `generators.json`, `upgrades.json`.
|
||
|
||
- **Selling:** customers want `baseDemand × demandMultiplier` units/s at the *fair price* and demand collapses above it. Raise the price when supply is short; buy marketing/demand upgrades to sell more.
|
||
- **Raw material** (wire, herbs…): the price per unit starts high (`startUnitPrice`, about a third of the sale price) and falls towards `floorUnitPrice` as total production grows (economies of scale). Lots grow with the operation, in doublings. The market is erratic: random swings every `updateEverySeconds` plus occasional shortages and gluts (`spikeChance`, `spikeSize`), with ▲/▼ next to the price showing its last move. The auto-buy upgrade is patient: it waits for a fair price unless stock is nearly gone.
|