A grocery price guessing game that tests how in touch you are with the cost of everyday goods
Features • Quick Start • Usage • Deployment
Note
This project is currently in active development. Only Singapore (SG) is supported at this time. More regions are on the roadmap — see Supported Countries.
Guess the real prices of common household items in your country and find out if you're down to earth or living in a bubble. Scored against crowd-sourced averages from real shoppers. 10 fixed items, every region, no sign-up required.
| Feature | Description |
|---|---|
| 10 fixed items | Same 10 everyday items across every country — only the prices change |
| Denomination support | Items like eggs, milk, and rice let you pick a pack size before guessing |
| Multi-country support | Items are universal; prices, brands, and leaderboards are country-scoped |
| Scoring system | Points based on accuracy — closer guess = higher score, +50 bonus for guessing under |
| Crowd-sourced prices | Answers are running averages of real user submissions |
| Leaderboard | Country + period-based leaderboard to compete against other players |
| Anonymous play | No sign-up required — anonymous user ID stored in localStorage |
| Nightly catalog sync | EventBridge cron updates brand names and images for the fixed items per country via Open Food Facts |
| Mobile-first UI | Game-show aesthetic with dark navy + gold theme, fully responsive |
| Software | Version | Purpose |
|---|---|---|
| Docker | Latest | Local DynamoDB |
| Node.js | 20.x | Backend + frontend dev servers |
| npm | Latest | Package management |
1. Clone the repository:
git clone https://github.com/NotYuSheng/How-Much.git
cd How-Much2. Install dependencies:
cd frontend && npm install && cd ..
cd backend && npm install && cd ..3. Start local DynamoDB:
docker compose up -d4. Bootstrap tables and seed data (first time only):
./scripts/bootstrap-local.shData is persisted in a Docker volume — you only need to run this once. Tables survive container restarts.
5. Start the dev servers:
# Terminal 1 — API server
cd backend && npm run dev
# Terminal 2 — React frontend
cd frontend && npm run dev6. Open the game:
Visit http://localhost:5173 in your browser.
graph LR
A[Select Region] --> B[Start Game]
B --> C[Guess Item Price]
C --> D[See Score & Reveal]
D --> E{More Items?}
E -- Yes --> C
E -- No --> F[Final Score]
F --> G[Submit to Leaderboard]
- Select Region — Pick your country on the start screen (Singapore live, more coming soon)
- Start — Click "Start Game" to begin a new session — all 10 items appear in a randomised order
- Guess — For denomination items, pick a pack size first, then enter your price guess
- Reveal — See the actual crowd-sourced price and your score for that round
- Repeat — Continue through all 10 items
- Final Score — See your total out of 10,000 and submit a nickname to the leaderboard
| Accuracy | Points |
|---|---|
| Exact (0%) | 1000 |
| Within 2% | 950 |
| Within 5% | 850 |
| Within 10% | 700 |
| Within 20% | 500 |
| Within 35% | 300 |
| Within 50% | 100 |
| Off by >50% | 0 |
| Guess ≤ actual | +50 bonus |
Maximum score per round: 1,000 pts — Maximum per game (10 rounds): 10,000 pts
| Component | Technology |
|---|---|
| Frontend | React 19, Tailwind CSS v4, Vite |
| Backend | Node.js 20.x Lambda functions (ESM), esbuild |
| Database | DynamoDB on-demand (integer cents to avoid float bugs) |
| Infrastructure | Terraform, AWS ap-southeast-1 |
| Local Dev | DynamoDB Local via Docker Compose |
| Catalog Sync | EventBridge nightly cron → Open Food Facts API |
The data model is fully country-aware from the ground up — no infrastructure changes needed to add a new country:
- Items have a
countriesStringSet (e.g.["SG", "MY"]) - Prices store
country+currencyper submission - Leaderboard PK includes country, so rankings are always country-scoped
- Catalog sync runs per country, pulling locally relevant products from Open Food Facts
To add a new country, run catalog sync with the new country code — no item changes needed.
Asia-Pacific
- Singapore (SG)
- China (CN)
- India (IN)
- Japan (JP)
- South Korea (KR)
- Indonesia (ID)
- Philippines (PH)
- Malaysia (MY)
- Australia (AU)
Europe
- United Kingdom (GB)
- Germany (DE)
- France (FR)
- Spain (ES)
- Italy (IT)
- Russia (RU)
Americas
- United States (US)
- Brazil (BR)
- Mexico (MX)
How-Much/
├── frontend/ # React + Tailwind (Vite)
│ └── src/
│ ├── components/ # StartScreen, Header, ItemCard, PriceInput,
│ │ # DenominationPicker, RevealOverlay, ProgressDots, FinalScore
│ ├── lib/
│ │ ├── api.js # All API calls + anonymous user ID
│ │ └── scoring.js # Score calculation + labels
│ └── App.jsx # Game state machine
├── backend/
│ ├── functions/
│ │ ├── game/ # POST /sessions, POST /sessions/:id/guess,
│ │ │ # POST /sessions/:id/complete, GET /sessions/:id
│ │ ├── items/ # GET /items, GET /items/:id
│ │ ├── prices/ # GET /prices/:itemId
│ │ ├── leaderboard/ # GET /leaderboard
│ │ └── catalog-sync/ # Nightly Open Food Facts → DynamoDB sync
│ ├── shared/
│ │ ├── dynamo.js # DynamoDB client
│ │ └── scoring.js # Scoring algorithm + HTTP helpers
│ └── scripts/
│ └── build.js # esbuild bundler
├── scripts/
│ ├── bootstrap-local.sh # Creates tables and seeds data locally
│ └── seed.js # Seeds curated items
├── terraform/ # Full AWS infrastructure as code
└── docker-compose.yml # DynamoDB Local for dev
POST /sessions # Start game, picks 10 random items by country
POST /sessions/{id}/guess # Submit guess (cents) → returns score + reveal
GET /sessions/{id} # Recover state on page refresh
POST /sessions/{id}/complete # Finalise session → submit to leaderboard
GET /items?country=SG
GET /items/{id}
GET /prices/{itemId}?country=SG
GET /leaderboard?country=SG&period=alltime
- AWS account with CLI configured
- Terraform installed
1. Bootstrap Terraform state backend (once):
aws s3 mb s3://howmuch-tfstate-<account-id> --region ap-southeast-1
aws dynamodb create-table --table-name howmuch-tfstate-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST --region ap-southeast-1Update terraform/backend.tf with your bucket name.
2. Build and deploy backend:
cd backend && npm run build
cd terraform && terraform init && terraform apply3. Build and deploy frontend:
cd frontend && npm run build
aws s3 sync dist/ s3://howmuch-frontend/ --delete
aws cloudfront create-invalidation --distribution-id <id> --paths "/*"4. Seed production data:
node scripts/seed.jsdocker compose logs -fcd backend && npm run buildnode scripts/seed.jsdocker compose down -v && docker compose up -d
./scripts/bootstrap-local.sh- Anonymous auth — No user accounts; identity is a UUID in localStorage
- No secrets in code — AWS credentials via IAM roles; environment variables for config
- DynamoDB not public — Only accessible via Lambda functions behind API Gateway
- Sessions expire — Session TTL is 24 hours for abandoned games
This project is licensed under the MIT License. See LICENSE for details.