121 lines
6.8 KiB
Markdown
121 lines
6.8 KiB
Markdown
# The Button
|
|
|
|
A web game of patience and nerve. A 24-hour countdown resets every time any player presses the button. If the countdown ever reaches zero, the game is over forever, and the final global stats are published.
|
|
|
|
Inspired by Reddit's [The Button](https://en.wikipedia.org/wiki/The_Button_(Reddit)) experiment.
|
|
|
|
## How to play
|
|
|
|
1. Sign up with a name and password (or log back in).
|
|
2. Press the button. The countdown resets to 24 hours.
|
|
3. You score points based on the zone the countdown was in when you pressed. The longer you wait, the more you score, but if everyone waits too long, the game ends.
|
|
|
|
### Zones
|
|
|
|
Higher-value zones are shorter, so scoring big is risky.
|
|
|
|
| Zone | Points | Duration (24h timer) |
|
|
|--------|-------:|---------------------:|
|
|
| Green | 0 | 12h 11m |
|
|
| Blue | 2 | 6h 6m |
|
|
| Yellow | 4 | 3h 3m |
|
|
| Red | 8 | 1h 31m |
|
|
| Black | 16 | 46m |
|
|
| White | 32 | 23m |
|
|
|
|
Every press records the zone, the points, and the exact remaining time in milliseconds. Players can view their own score, presses per zone, and press history at any time. A public live leaderboard shows all players ranked by total points, with press counts; ties are sorted alphabetically. Scores refresh every five seconds and immediately after you press or join. Detailed global stats (presses per zone, closest call, full press log) stay hidden until the game ends.
|
|
|
|
Logged-in players can also join the mailing list.
|
|
|
|
The Chat section is a simple public chatroom. Logged-in players can post plain-text messages (up to 200 characters, one message every two seconds). The latest 100 messages are displayed oldest first and refresh every five seconds while Chat is open. Messages persist in SQLite across restarts, including after the game ends. There are no editing, deletion, or moderation controls yet.
|
|
|
|
## Running
|
|
|
|
### Docker (recommended)
|
|
|
|
```sh
|
|
docker compose up -d --build
|
|
```
|
|
|
|
Open http://localhost:8000. The SQLite game database and the mailing list are stored in the `button-data` volume. Existing JSON game data in that volume is automatically migrated on the first startup after upgrading.
|
|
|
|
```sh
|
|
docker compose logs -f # view logs
|
|
docker compose cp thebutton:/data/mailing_list.csv . # export the mailing list
|
|
docker compose down # stop (data is kept)
|
|
docker compose down -v # stop and delete all data (new game)
|
|
```
|
|
|
|
### Without Docker
|
|
|
|
Requires Python 3.9+. No third-party packages.
|
|
|
|
```sh
|
|
python server.py
|
|
```
|
|
|
|
Open http://127.0.0.1:8000. Game state is stored in `data.sqlite3`.
|
|
|
|
### Migrating existing data
|
|
|
|
On startup, if the database has no saved game and `data.json` exists, the server automatically imports the countdown, players, password hashes, active sessions, subscription flags, and complete press history in one transaction. Older single-token player records are also supported. No manual conversion or additional dependencies are needed.
|
|
|
|
The JSON file is left untouched as a backup. Once the database contains a game, it is authoritative: subsequent restarts never re-import the JSON file. Invalid source data causes startup to fail rather than silently starting a new game; fix the source and restart to retry. The mailing-list CSV is unchanged.
|
|
|
|
Custom `BUTTON_DATA_FILE` paths continue to locate legacy JSON data; by default, the SQLite database is created alongside it with a `.sqlite3` suffix. `BUTTON_DATABASE_FILE` can override the database path. Docker keeps both paths under `/data`, so existing named volumes migrate automatically.
|
|
|
|
To start a new game, stop the server and delete the SQLite database **and remove or move the legacy JSON backup**; leaving the JSON file in place would import the old game again.
|
|
|
|
## Configuration
|
|
|
|
Set via environment variables (in Docker, add them under `environment:` in `compose.yaml`).
|
|
|
|
| Variable | Default | Description |
|
|
|-----------------------------|--------------------------|--------------------------------------|
|
|
| `BUTTON_DURATION_SECONDS` | `86400` | Countdown length. Zones scale with it. |
|
|
| `BUTTON_HOST` | `127.0.0.1` (`0.0.0.0` in Docker) | Address to listen on. |
|
|
| `BUTTON_PORT` | `8000` | Port to listen on. |
|
|
| `BUTTON_DATA_FILE` | `data.json` | Legacy JSON file to import if no saved SQLite game exists. |
|
|
| `BUTTON_DATABASE_FILE` | `data.sqlite3` | SQLite game database; defaults to `BUTTON_DATA_FILE` with a `.sqlite3` suffix. |
|
|
| `BUTTON_MAILING_LIST_FILE` | `mailing_list.csv` | Mailing list file. |
|
|
|
|
Tip: `BUTTON_DURATION_SECONDS=60` makes for quick testing.
|
|
|
|
## API
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------------------|------|-----------------------------------------------|
|
|
| GET | `/api/state` | | Deadline, server time, zones, whether ended |
|
|
| POST | `/api/register` | | `{name, password}` → `{name, token}` |
|
|
| POST | `/api/login` | | `{name, password}` → `{name, token}` |
|
|
| POST | `/api/logout` | ✓ | Revoke the current session token |
|
|
| POST | `/api/press` | ✓ | Press the button |
|
|
| GET | `/api/me` | ✓ | Your score, zone counts, and press history |
|
|
| GET | `/api/leaderboard` | | Public player names, total points, and press counts |
|
|
| GET | `/api/chat` | | Latest 100 public chat messages |
|
|
| POST | `/api/chat` | ✓ | `{message}` → saved message; 200-character limit |
|
|
| GET | `/api/stats` | | Global stats (403 until the game ends) |
|
|
| POST | `/api/subscribe` | ✓ | `{first_name, email, last_name?, phone?}` |
|
|
|
|
Authenticated requests use `Authorization: Bearer <token>`.
|
|
|
|
## Security notes
|
|
|
|
- Passwords are hashed with PBKDF2-SHA256 (600k iterations); session tokens are stored only as SHA-256 hashes.
|
|
- Logins lock for 15 minutes after 5 failed attempts per name.
|
|
- The server speaks plain HTTP. **Put it behind a TLS-terminating reverse proxy** (e.g. Caddy or nginx) before exposing it to the internet.
|
|
- Registration and mailing-list signup are not rate limited.
|
|
|
|
## Project layout
|
|
|
|
```
|
|
server.py Game server and API (Python standard library only)
|
|
static/ Frontend (HTML, CSS, vanilla JS)
|
|
Dockerfile Container image
|
|
compose.yaml Docker Compose service and data volume
|
|
```
|
|
|
|
## Credits
|
|
|
|
Created by [Eric Meehan](https://www.eom.dev/). Built with [GitHub Copilot](https://github.com/features/copilot).
|