mirror of
https://github.com/tiennm99/rplace.git
synced 2026-05-26 17:55:54 +00:00
f97ca4d34d484b59da988fb97fca9867f697ebd6
Introduce REDIS_KEY_PREFIX constant to namespace all Redis keys under 'rplace:', preventing collisions in shared Redis instances.
rplace
A collaborative pixel art canvas inspired by Reddit's r/place. Place pixels, create art together in real-time.
Features
- 2048x2048 canvas with 32-color palette (from rplace.live)
- Real-time updates via WebSocket (Cloudflare Durable Objects)
- Batch pixel placement up to 32 pixels per request
- Stackable credit system — earn 1 pixel/sec, stack up to 256, spend in batches
- Zoom/pan with mouse wheel + drag (desktop) and pinch-zoom + drag (mobile)
- Long-press to place on touch devices
- Credit bar with visual regeneration feedback
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | Svelte 5 (runes) + HTML5 Canvas |
| Backend | Hono on Cloudflare Workers |
| Real-time | WebSocket via Cloudflare Durable Objects |
| Storage | Upstash Redis (BITFIELD for canvas, Lua for rate limiting) |
| Build | Vite |
Architecture
Browser (Svelte SPA + WebSocket)
| GET /api/canvas → full canvas binary (2.5MB raw)
| POST /api/place → batch pixel placement
| WS /api/ws → Durable Object broadcast room
v
Cloudflare Worker (Hono)
├── Canvas API (read/write pixels via Redis BITFIELD)
├── Rate Limiter (Lua script, atomic token bucket)
└── Durable Object (WebSocket broadcast to all clients)
↕
Upstash Redis
├── BITFIELD "canvas" (5-bit per pixel, 2048x2048 = 2.62MB)
└── HASH "credits:{userId}" (lastUpdate + credits)
Getting Started
Prerequisites
- Node.js 18+
- Cloudflare account (free tier works)
- Upstash Redis database (free tier works)
Setup
# Clone and install
git clone <repo-url>
cd rplace
npm install
# Configure environment
cp .env.example .env
# Edit .env with your Upstash Redis credentials
# For wrangler (Cloudflare Workers CLI)
npx wrangler secret put UPSTASH_REDIS_REST_URL
npx wrangler secret put UPSTASH_REDIS_REST_TOKEN
Development
# Run worker locally (serves both API and frontend)
npm run dev
# Or run frontend and worker separately
npm run dev:client # Vite dev server on :5173 (proxies /api to :8787)
npm run dev # Wrangler dev server on :8787
Deploy
npm run deploy # Builds frontend + deploys worker to Cloudflare
Project Structure
src/
├── worker.js # Hono API entry point
├── durable-objects/
│ └── canvas-room.js # WebSocket broadcast room
├── lib/
│ ├── constants.js # Config, palette, limits (shared)
│ ├── redis-client.js # Upstash Redis factory
│ ├── canvas-storage.js # BITFIELD read/write
│ ├── canvas-decoder.js # 5-bit → RGBA (client-side)
│ ├── rate-limiter.js # Lua token bucket
│ └── get-user-id.js # IP-based identity
├── client/
│ ├── main.js # Svelte mount
│ ├── App.svelte # Root + WebSocket + credit timer
│ ├── app.css # Global styles
│ └── components/
│ ├── CanvasRenderer.svelte # Canvas + zoom/pan + touch
│ ├── ColorPicker.svelte # 32-color palette grid
│ ├── CanvasControls.svelte # Zoom buttons + coordinates
│ └── UserInfo.svelte # Credit counter + bar
└── index.html # Vite entry
API
GET /api/canvas
Returns the full canvas as raw binary (5-bit packed, ~2.5MB).
POST /api/place
Place pixels on the canvas.
{
"pixels": [
{ "x": 100, "y": 200, "color": 27 }
]
}
Response: { "ok": true, "credits": 255 }
Errors:
400— invalid pixel data or batch > 32429— rate limited (includesretryAfterseconds)
WS /api/ws
WebSocket for real-time pixel updates. Messages are JSON:
{ "type": "pixels", "pixels": [{ "x": 100, "y": 200, "color": 27 }] }
Configuration
Key constants in src/lib/constants.js:
| Constant | Default | Description |
|---|---|---|
CANVAS_WIDTH |
2048 | Canvas width in pixels |
CANVAS_HEIGHT |
2048 | Canvas height in pixels |
MAX_COLORS |
32 | Number of colors in palette |
MAX_BATCH_SIZE |
32 | Max pixels per placement request |
MAX_CREDITS |
256 | Max stackable credits |
CREDIT_REGEN_RATE |
1 | Credits earned per second |
Credits & References
- Reddit on Building & Scaling r/place (Fastly)
- Engineering Behind r/place (Sai Kumar Chintada)
- Redis Place: Building r/place with 9 Redis Data Structures (Mehdi Amrane)
- Redis Pixel War (Alfredo Salzillo)
- Redis BITFIELD Command
- reddit-plugin-place-opensource
- rPlace by anthonytedja
- redis-place by mehdiamrane
- redis-challenge by alfredosalzillo
- place by dynastic
- rplace.live — color palette reference
License
MIT
Description
Languages
JavaScript
61.1%
Svelte
38.6%
HTML
0.2%
CSS
0.1%