Skip to content

mbuckingham74/text-scramble

Repository files navigation

Word Twist

A modern web-based word puzzle game

Play Now

Node.js React MySQL Redis Docker License


Unscramble letters. Find words. Beat the clock. Improve touch typing skills


About the Game

Word Twist presents you with scrambled letters and challenges you to find all valid English words that can be formed from them. The game features progressive difficulty — puzzles start with 6 letters and grow to 7 and then 8 letters as you advance through levels.

Game Modes

Mode Description
Timed Race against the clock with 2 minutes per round
Untimed Take your time and find every word

Controls

Key Action
A-Z Type letters
Enter Submit word
Space Shuffle letters
Backspace Delete last letter
Tab Clear selection

Progressive Difficulty

Level Letters Words to Find
1-5 6 letters 3-6 letter words
6-10 7 letters 3-7 letter words
11+ 8 letters 3-8 letter words

Scoring

Word Length Points
3 letters 30 pts
4 letters 45 pts
5 letters 60 pts
6 letters 75 pts
7 letters 90 pts
8 letters 105 pts

Tip: Find at least one full-length word to advance to the next level!


Tech Stack

React
React
Frontend
Express
Express
Backend API
MySQL
MySQL 8
Database
Redis
Redis
Rate Limiting
Docker
Docker
Deployment

Getting Started

Prerequisites

  • Node.js 20+
  • Docker & Docker Compose (for full stack)
  • MySQL 8 (if running without Docker)

Quick Start with Docker

# Clone and run
git clone https://github.com/mbuckingham74/text-scramble.git
cd text-scramble

# Set up environment
cp .env.example .env
# Edit .env with your JWT_SECRET

# Launch
docker compose up -d --build

Development Setup

# Install dependencies
cd frontend && npm install
cd ../backend && npm install

# Start backend (Terminal 1)
cd backend && npm run dev

# Start frontend (Terminal 2)
cd frontend && npm start

The frontend runs on http://localhost:3000 and proxies API requests to the backend on port 3001.

Environment Variables

Variable Required Description
JWT_SECRET Yes Secret key for JWT tokens
DB_HOST Yes MySQL host
DB_USER Yes MySQL username
DB_PASSWORD Yes MySQL password
DB_NAME Yes MySQL database name
REDIS_URL No Redis URL for rate limiting
NODE_ENV No Set to production for prod
# Generate a secure JWT secret
openssl rand -base64 32

Project Structure

word-twist/
├── frontend/           # React application
│   ├── src/
│   │   ├── App.js      # Main game component
│   │   ├── App.css     # Styles
│   │   └── sounds.js   # Sound effects
│   ├── Dockerfile
│   └── nginx.conf
├── backend/            # Express API
│   ├── src/
│   │   ├── index.js    # API endpoints
│   │   ├── game.js     # Game logic
│   │   ├── auth.js     # JWT authentication
│   │   └── words.txt   # Dictionary (~53K words, 3-8 letters)
│   ├── init.sql        # Database schema
│   └── Dockerfile
├── docker-compose.yml
└── CLAUDE_CONTEXT.md   # Detailed project documentation

API Reference

Method Endpoint Auth Description
GET /api/puzzle?level=N Generate puzzle for given level
POST /api/validate Validate a word submission
POST /api/solutions Get all valid words for letters
POST /api/register Register a new user
POST /api/login User login
POST /api/scores Required Submit a score
GET /api/leaderboard Get top 10 scores per mode
GET /api/scores/me Required Get current user's scores

Security Features

  • JWT authentication for score submission
  • Redis-backed rate limiting with automatic fallback
  • Zod input validation on all endpoints
  • CORS restricted to approved origins
  • bcrypt password hashing (10 rounds)
  • Multi-proxy chain support (Cloudflare/NPM)
How Rate Limiting Works
┌─────────────────────────────────────────────────────────────────────────────┐
│                              REQUEST FLOW                                   │
└─────────────────────────────────────────────────────────────────────────────┘

    User Request
         │
         ▼
┌─────────────────┐
│   Cloudflare    │  ◄── Adds X-Forwarded-For header with real IP
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│  Nginx Proxy    │  ◄── SSL termination, forwards to backend
│    Manager      │
└────────┬────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                         WORD TWIST BACKEND                                  │
│                                                                             │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │  app.set('trust proxy', 2)  // 2 proxies: Cloudflare → NPM            │  │
│  │  ► Extracts real client IP from X-Forwarded-For chain                │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                    │                                        │
│                                    ▼                                        │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │  RATE LIMITER MIDDLEWARE                                             │  │
│  │                                                                      │  │
│  │  For each request:                                                   │  │
│  │  1. Get client IP                                                    │  │
│  │  2. Build key: "wordtwist:game:192.168.1.1"                         │  │
│  │  3. Check Redis for current count                                    │  │
│  │  4. If under limit → increment & allow                               │  │
│  │  5. If over limit → return 429 "Too Many Requests"                   │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                          │                   │                              │
│              ┌───────────┘                   └───────────┐                  │
│              │ Redis Available                Redis Down │                  │
│              ▼                                          ▼                   │
│  ┌─────────────────────┐                  ┌─────────────────────┐          │
│  │   REDIS STORE       │                  │   MEMORY STORE      │          │
│  │   (Persistent)      │                  │   (Fallback)        │          │
│  └─────────────────────┘                  └─────────────────────┘          │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│                           REDIS (Database 1)                                │
│                                                                             │
│  Keys stored with auto-expiry:                                              │
│                                                                             │
│  wordtwist:auth:192.168.1.1     → { count: 3, resetTime: ... }             │
│  wordtwist:game:192.168.1.1     → { count: 47, resetTime: ... }            │
│  wordtwist:score:192.168.1.1    → { count: 2, resetTime: ... }             │
│  wordtwist:general:192.168.1.1  → { count: 15, resetTime: ... }            │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────────────┐
│                         RATE LIMITS                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  Endpoint Type        Limit             Window      Key Prefix              │
│  ─────────────────────────────────────────────────────────────────────────  │
│  Auth (login/reg)     10 requests       15 min      wordtwist:auth:         │
│  Game (puzzle)        300 requests      1 min       wordtwist:game:         │
│  Score submit         20 requests       1 min       wordtwist:score:        │
│  General (other)      200 requests      1 min       wordtwist:general:      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.


Play Word Twist

Inspired by the classic Text Twist game

About

Scramble text to guess words while learning to touch type

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages