removed old js backend
Browse filesSigned-off-by: Vimal Kumar <[email protected]>
- .gitignore +6 -6
- Dockerfile +0 -5
- crossword-app/backend/.env.example +0 -68
- crossword-app/backend/data/word-lists/animals.json +0 -165
- crossword-app/backend/data/word-lists/geography.json +0 -161
- crossword-app/backend/data/word-lists/science.json +0 -170
- crossword-app/backend/data/word-lists/technology.json +0 -221
- crossword-app/backend/package-lock.json +0 -0
- crossword-app/backend/package.json +0 -53
- crossword-app/backend/public/assets/index-Bkj8ir_U.js +0 -10
- crossword-app/backend/public/assets/index-Bkj8ir_U.js.map +0 -1
- crossword-app/backend/public/assets/index-V4v18wFW.css +0 -1
- crossword-app/backend/public/assets/vendor-nf7bT_Uh.js +0 -0
- crossword-app/backend/public/assets/vendor-nf7bT_Uh.js.map +0 -0
- crossword-app/backend/public/index.html +0 -16
- crossword-app/backend/setup-env.sh +0 -33
- crossword-app/backend/src/app.js +0 -175
- crossword-app/backend/src/controllers/puzzleController.js +0 -65
- crossword-app/backend/src/models/Topic.js +0 -144
- crossword-app/backend/src/models/Word.js +0 -112
- crossword-app/backend/src/routes/api.js +0 -82
- crossword-app/backend/src/services/crosswordGenerator.js +0 -982
- crossword-app/backend/src/services/embeddingWordService.js +0 -635
- crossword-app/backend/src/services/wordService.js +0 -131
- crossword-app/backend/src/test-ai-integration.js +0 -116
- crossword-app/backend/src/test-embedding.js +0 -63
.gitignore
CHANGED
@@ -3,12 +3,12 @@ node_modules/
|
|
3 |
*/node_modules/
|
4 |
|
5 |
# Environment variables
|
6 |
-
|
7 |
-
.env.local
|
8 |
-
.env.development.local
|
9 |
-
.env.test.local
|
10 |
-
.env.production.local
|
11 |
-
.env.backup
|
12 |
|
13 |
# Additional security - API keys and secrets
|
14 |
*.key
|
|
|
3 |
*/node_modules/
|
4 |
|
5 |
# Environment variables
|
6 |
+
#.env
|
7 |
+
# .env.local
|
8 |
+
# .env.development.local
|
9 |
+
# .env.test.local
|
10 |
+
# .env.production.local
|
11 |
+
# .env.backup
|
12 |
|
13 |
# Additional security - API keys and secrets
|
14 |
*.key
|
Dockerfile
CHANGED
@@ -30,7 +30,6 @@ RUN pip install --no-cache-dir --upgrade pip && \
|
|
30 |
|
31 |
# Copy all source code
|
32 |
COPY crossword-app/frontend/ ./frontend/
|
33 |
-
COPY crossword-app/backend/ ./backend/
|
34 |
COPY crossword-app/backend-py/ ./backend-py/
|
35 |
|
36 |
# Copy cache directory with pre-built models and NLTK data
|
@@ -44,9 +43,6 @@ RUN cd frontend && npm run build
|
|
44 |
# Copy built frontend files to Python backend public directory
|
45 |
RUN mkdir -p backend-py/public && cp -r frontend/dist/* backend-py/public/
|
46 |
|
47 |
-
# Create symlink for shared data (word lists)
|
48 |
-
RUN cd backend-py && ln -sf ../backend/data data
|
49 |
-
|
50 |
# Stage 2: Runtime - Copy only necessary files as non-root user
|
51 |
FROM python:3.11-slim AS runtime
|
52 |
|
@@ -67,7 +63,6 @@ WORKDIR /app/backend-py
|
|
67 |
|
68 |
# Copy built application files with correct ownership
|
69 |
COPY --from=builder --chown=appuser:appuser /app/backend-py ./
|
70 |
-
COPY --from=builder --chown=appuser:appuser /app/backend/data ./data
|
71 |
|
72 |
# Copy cache directory to backend-py/cache for model and NLTK data
|
73 |
COPY --from=builder --chown=appuser:appuser /app/cache-dir /app/backend-py/cache
|
|
|
30 |
|
31 |
# Copy all source code
|
32 |
COPY crossword-app/frontend/ ./frontend/
|
|
|
33 |
COPY crossword-app/backend-py/ ./backend-py/
|
34 |
|
35 |
# Copy cache directory with pre-built models and NLTK data
|
|
|
43 |
# Copy built frontend files to Python backend public directory
|
44 |
RUN mkdir -p backend-py/public && cp -r frontend/dist/* backend-py/public/
|
45 |
|
|
|
|
|
|
|
46 |
# Stage 2: Runtime - Copy only necessary files as non-root user
|
47 |
FROM python:3.11-slim AS runtime
|
48 |
|
|
|
63 |
|
64 |
# Copy built application files with correct ownership
|
65 |
COPY --from=builder --chown=appuser:appuser /app/backend-py ./
|
|
|
66 |
|
67 |
# Copy cache directory to backend-py/cache for model and NLTK data
|
68 |
COPY --from=builder --chown=appuser:appuser /app/cache-dir /app/backend-py/cache
|
crossword-app/backend/.env.example
DELETED
@@ -1,68 +0,0 @@
|
|
1 |
-
# Crossword App - Environment Configuration Template
|
2 |
-
# Copy this file to .env and update with your actual values
|
3 |
-
|
4 |
-
# Environment Configuration
|
5 |
-
NODE_ENV=development
|
6 |
-
PORT=3001
|
7 |
-
|
8 |
-
# Database Configuration (Optional - using JSON files currently)
|
9 |
-
DATABASE_URL=postgresql://username:password@localhost:5432/crossword_db
|
10 |
-
DB_HOST=localhost
|
11 |
-
DB_PORT=5432
|
12 |
-
DB_NAME=crossword_db
|
13 |
-
DB_USER=username
|
14 |
-
DB_PASSWORD=password
|
15 |
-
|
16 |
-
# CORS Configuration
|
17 |
-
CORS_ORIGIN=http://localhost:5173,http://localhost:3000
|
18 |
-
|
19 |
-
# Rate Limiting
|
20 |
-
RATE_LIMIT_WINDOW_MS=900000
|
21 |
-
RATE_LIMIT_MAX_REQUESTS=100
|
22 |
-
GENERATE_RATE_LIMIT_MAX=10
|
23 |
-
|
24 |
-
# Security
|
25 |
-
JWT_SECRET=your-jwt-secret-key-change-in-production
|
26 |
-
|
27 |
-
# Logging
|
28 |
-
LOG_LEVEL=info
|
29 |
-
|
30 |
-
# Cache Configuration (Optional - for Redis)
|
31 |
-
REDIS_URL=redis://localhost:6379
|
32 |
-
CACHE_TTL=3600
|
33 |
-
|
34 |
-
# External APIs (Optional)
|
35 |
-
DICTIONARY_API_KEY=your-dictionary-api-key
|
36 |
-
WIKIPEDIA_API_ENDPOINT=https://en.wikipedia.org/api/rest_v1
|
37 |
-
|
38 |
-
# =========================================
|
39 |
-
# 🤖 AI/HUGGINGFACE CONFIGURATION
|
40 |
-
# =========================================
|
41 |
-
|
42 |
-
# HuggingFace API Configuration
|
43 |
-
# Get your free API key at: https://huggingface.co/settings/tokens
|
44 |
-
HUGGINGFACE_API_KEY=hf_xxxxxxxxxx
|
45 |
-
|
46 |
-
# HuggingFace Model Configuration
|
47 |
-
HUGGINGFACE_MODEL_ENDPOINT=https://api-inference.huggingface.co/models/sentence-transformers/all-MiniLM-L6-v2
|
48 |
-
EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
49 |
-
|
50 |
-
# LLM Configuration for Clue Generation
|
51 |
-
TEXT_GENERATION_MODEL=gpt2
|
52 |
-
MAX_WORDS_PER_GENERATION=15
|
53 |
-
CACHE_EMBEDDINGS=true
|
54 |
-
|
55 |
-
# AI Word Generation Settings
|
56 |
-
WORD_SIMILARITY_THRESHOLD=0.7
|
57 |
-
MAX_TOPIC_WORDS=50
|
58 |
-
FALLBACK_TO_STATIC=true
|
59 |
-
USE_AI_WORDS=false
|
60 |
-
|
61 |
-
# =========================================
|
62 |
-
# 🚀 QUICK START INSTRUCTIONS
|
63 |
-
# =========================================
|
64 |
-
# 1. Copy this file: cp .env.example .env
|
65 |
-
# 2. Get HuggingFace API key: https://huggingface.co/settings/tokens
|
66 |
-
# 3. Replace HUGGINGFACE_API_KEY with your real key
|
67 |
-
# 4. Optionally set USE_AI_WORDS=true to enable AI by default
|
68 |
-
# 5. Start server: npm run dev
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/data/word-lists/animals.json
DELETED
@@ -1,165 +0,0 @@
|
|
1 |
-
[
|
2 |
-
{ "word": "DOG", "clue": "Man's best friend" },
|
3 |
-
{ "word": "CAT", "clue": "Feline pet that purrs" },
|
4 |
-
{ "word": "ELEPHANT", "clue": "Large mammal with a trunk" },
|
5 |
-
{ "word": "TIGER", "clue": "Striped big cat" },
|
6 |
-
{ "word": "WHALE", "clue": "Largest marine mammal" },
|
7 |
-
{ "word": "BUTTERFLY", "clue": "Colorful flying insect" },
|
8 |
-
{ "word": "BIRD", "clue": "Flying creature with feathers" },
|
9 |
-
{ "word": "FISH", "clue": "Aquatic animal with gills" },
|
10 |
-
{ "word": "LION", "clue": "King of the jungle" },
|
11 |
-
{ "word": "BEAR", "clue": "Large mammal that hibernates" },
|
12 |
-
{ "word": "RABBIT", "clue": "Hopping mammal with long ears" },
|
13 |
-
{ "word": "HORSE", "clue": "Riding animal with hooves" },
|
14 |
-
{ "word": "SHEEP", "clue": "Woolly farm animal" },
|
15 |
-
{ "word": "GOAT", "clue": "Horned farm animal" },
|
16 |
-
{ "word": "DUCK", "clue": "Water bird that quacks" },
|
17 |
-
{ "word": "CHICKEN", "clue": "Farm bird that lays eggs" },
|
18 |
-
{ "word": "SNAKE", "clue": "Slithering reptile" },
|
19 |
-
{ "word": "TURTLE", "clue": "Shelled reptile" },
|
20 |
-
{ "word": "FROG", "clue": "Amphibian that croaks" },
|
21 |
-
{ "word": "SHARK", "clue": "Predatory ocean fish" },
|
22 |
-
{ "word": "DOLPHIN", "clue": "Intelligent marine mammal" },
|
23 |
-
{ "word": "PENGUIN", "clue": "Flightless Antarctic bird" },
|
24 |
-
{ "word": "MONKEY", "clue": "Primate that swings in trees" },
|
25 |
-
{ "word": "ZEBRA", "clue": "Striped African animal" },
|
26 |
-
{ "word": "GIRAFFE", "clue": "Tallest land animal" },
|
27 |
-
{ "word": "WOLF", "clue": "Wild canine that howls" },
|
28 |
-
{ "word": "FOX", "clue": "Cunning red-furred animal" },
|
29 |
-
{ "word": "DEER", "clue": "Graceful forest animal with antlers" },
|
30 |
-
{ "word": "MOOSE", "clue": "Large antlered animal" },
|
31 |
-
{ "word": "SQUIRREL", "clue": "Tree-climbing nut gatherer" },
|
32 |
-
{ "word": "RACCOON", "clue": "Masked nocturnal animal" },
|
33 |
-
{ "word": "BEAVER", "clue": "Dam-building rodent" },
|
34 |
-
{ "word": "OTTER", "clue": "Playful water mammal" },
|
35 |
-
{ "word": "SEAL", "clue": "Marine mammal with flippers" },
|
36 |
-
{ "word": "WALRUS", "clue": "Tusked Arctic marine mammal" },
|
37 |
-
{ "word": "RHINO", "clue": "Horned thick-skinned mammal" },
|
38 |
-
{ "word": "HIPPO", "clue": "Large African river mammal" },
|
39 |
-
{ "word": "CHEETAH", "clue": "Fastest land animal" },
|
40 |
-
{ "word": "LEOPARD", "clue": "Spotted big cat" },
|
41 |
-
{ "word": "JAGUAR", "clue": "South American big cat" },
|
42 |
-
{ "word": "PUMA", "clue": "Mountain lion" },
|
43 |
-
{ "word": "LYNX", "clue": "Wild cat with tufted ears" },
|
44 |
-
{ "word": "KANGAROO", "clue": "Hopping Australian marsupial" },
|
45 |
-
{ "word": "KOALA", "clue": "Eucalyptus-eating marsupial" },
|
46 |
-
{ "word": "PANDA", "clue": "Black and white bamboo eater" },
|
47 |
-
{ "word": "SLOTH", "clue": "Slow-moving tree dweller" },
|
48 |
-
{ "word": "ARMADILLO", "clue": "Armored mammal" },
|
49 |
-
{ "word": "ANTEATER", "clue": "Long-snouted insect eater" },
|
50 |
-
{ "word": "PLATYPUS", "clue": "Egg-laying mammal with a bill" },
|
51 |
-
{ "word": "BAT", "clue": "Flying mammal" },
|
52 |
-
{ "word": "MOLE", "clue": "Underground tunnel digger" },
|
53 |
-
{ "word": "HEDGEHOG", "clue": "Spiny small mammal" },
|
54 |
-
{ "word": "PORCUPINE", "clue": "Quill-covered rodent" },
|
55 |
-
{ "word": "SKUNK", "clue": "Black and white scent-spraying mammal" },
|
56 |
-
{ "word": "WEASEL", "clue": "Small carnivorous mammal" },
|
57 |
-
{ "word": "BADGER", "clue": "Burrowing black and white mammal" },
|
58 |
-
{ "word": "FERRET", "clue": "Domesticated hunting animal" },
|
59 |
-
{ "word": "MINK", "clue": "Valuable fur-bearing animal" },
|
60 |
-
{ "word": "EAGLE", "clue": "Majestic bird of prey" },
|
61 |
-
{ "word": "HAWK", "clue": "Sharp-eyed hunting bird" },
|
62 |
-
{ "word": "OWL", "clue": "Nocturnal bird with large eyes" },
|
63 |
-
{ "word": "FALCON", "clue": "Fast diving bird of prey" },
|
64 |
-
{ "word": "VULTURE", "clue": "Scavenging bird" },
|
65 |
-
{ "word": "CROW", "clue": "Black intelligent bird" },
|
66 |
-
{ "word": "RAVEN", "clue": "Large black corvid" },
|
67 |
-
{ "word": "ROBIN", "clue": "Red-breasted songbird" },
|
68 |
-
{ "word": "SPARROW", "clue": "Small brown songbird" },
|
69 |
-
{ "word": "CARDINAL", "clue": "Bright red songbird" },
|
70 |
-
{ "word": "BLUEJAY", "clue": "Blue crested bird" },
|
71 |
-
{ "word": "WOODPECKER", "clue": "Tree-pecking bird" },
|
72 |
-
{ "word": "HUMMINGBIRD", "clue": "Tiny fast-flying bird" },
|
73 |
-
{ "word": "PELICAN", "clue": "Large-billed water bird" },
|
74 |
-
{ "word": "FLAMINGO", "clue": "Pink wading bird" },
|
75 |
-
{ "word": "STORK", "clue": "Long-legged wading bird" },
|
76 |
-
{ "word": "HERON", "clue": "Tall fishing bird" },
|
77 |
-
{ "word": "CRANE", "clue": "Large wading bird" },
|
78 |
-
{ "word": "SWAN", "clue": "Elegant white water bird" },
|
79 |
-
{ "word": "GOOSE", "clue": "Large waterfowl" },
|
80 |
-
{ "word": "TURKEY", "clue": "Large ground bird" },
|
81 |
-
{ "word": "PHEASANT", "clue": "Colorful game bird" },
|
82 |
-
{ "word": "QUAIL", "clue": "Small ground bird" },
|
83 |
-
{ "word": "PEACOCK", "clue": "Bird with spectacular tail feathers" },
|
84 |
-
{ "word": "OSTRICH", "clue": "Largest flightless bird" },
|
85 |
-
{ "word": "EMU", "clue": "Australian flightless bird" },
|
86 |
-
{ "word": "KIWI", "clue": "Small flightless New Zealand bird" },
|
87 |
-
{ "word": "PARROT", "clue": "Colorful talking bird" },
|
88 |
-
{ "word": "TOUCAN", "clue": "Large-billed tropical bird" },
|
89 |
-
{ "word": "MACAW", "clue": "Large colorful parrot" },
|
90 |
-
{ "word": "COCKATOO", "clue": "Crested parrot" },
|
91 |
-
{ "word": "CANARY", "clue": "Yellow singing bird" },
|
92 |
-
{ "word": "FINCH", "clue": "Small seed-eating bird" },
|
93 |
-
{ "word": "PIGEON", "clue": "Common city bird" },
|
94 |
-
{ "word": "DOVE", "clue": "Symbol of peace" },
|
95 |
-
{ "word": "SEAGULL", "clue": "Coastal scavenging bird" },
|
96 |
-
{ "word": "ALBATROSS", "clue": "Large ocean bird" },
|
97 |
-
{ "word": "PUFFIN", "clue": "Colorful-billed seabird" },
|
98 |
-
{ "word": "LIZARD", "clue": "Small scaly reptile" },
|
99 |
-
{ "word": "IGUANA", "clue": "Large tropical lizard" },
|
100 |
-
{ "word": "GECKO", "clue": "Wall-climbing lizard" },
|
101 |
-
{ "word": "CHAMELEON", "clue": "Color-changing reptile" },
|
102 |
-
{ "word": "ALLIGATOR", "clue": "Large American crocodilian" },
|
103 |
-
{ "word": "CROCODILE", "clue": "Large aquatic reptile" },
|
104 |
-
{ "word": "PYTHON", "clue": "Large constricting snake" },
|
105 |
-
{ "word": "COBRA", "clue": "Venomous hooded snake" },
|
106 |
-
{ "word": "VIPER", "clue": "Poisonous snake" },
|
107 |
-
{ "word": "RATTLESNAKE", "clue": "Snake with warning tail" },
|
108 |
-
{ "word": "SALAMANDER", "clue": "Amphibian that can regrow limbs" },
|
109 |
-
{ "word": "NEWT", "clue": "Small aquatic salamander" },
|
110 |
-
{ "word": "TOAD", "clue": "Warty amphibian" },
|
111 |
-
{ "word": "TADPOLE", "clue": "Frog larva" },
|
112 |
-
{ "word": "SALMON", "clue": "Fish that swims upstream" },
|
113 |
-
{ "word": "TROUT", "clue": "Freshwater game fish" },
|
114 |
-
{ "word": "BASS", "clue": "Popular sport fish" },
|
115 |
-
{ "word": "TUNA", "clue": "Large ocean fish" },
|
116 |
-
{ "word": "SWORDFISH", "clue": "Fish with long pointed bill" },
|
117 |
-
{ "word": "MARLIN", "clue": "Large billfish" },
|
118 |
-
{ "word": "MANTA", "clue": "Large ray fish" },
|
119 |
-
{ "word": "STINGRAY", "clue": "Flat fish with barbed tail" },
|
120 |
-
{ "word": "EEL", "clue": "Snake-like fish" },
|
121 |
-
{ "word": "SEAHORSE", "clue": "Horse-shaped fish" },
|
122 |
-
{ "word": "ANGELFISH", "clue": "Colorful tropical fish" },
|
123 |
-
{ "word": "GOLDFISH", "clue": "Common pet fish" },
|
124 |
-
{ "word": "CLOWNFISH", "clue": "Orange and white anemone fish" },
|
125 |
-
{ "word": "JELLYFISH", "clue": "Transparent stinging sea creature" },
|
126 |
-
{ "word": "OCTOPUS", "clue": "Eight-armed sea creature" },
|
127 |
-
{ "word": "SQUID", "clue": "Ten-armed cephalopod" },
|
128 |
-
{ "word": "CRAB", "clue": "Sideways-walking crustacean" },
|
129 |
-
{ "word": "LOBSTER", "clue": "Large marine crustacean" },
|
130 |
-
{ "word": "SHRIMP", "clue": "Small crustacean" },
|
131 |
-
{ "word": "STARFISH", "clue": "Five-armed sea creature" },
|
132 |
-
{ "word": "URCHIN", "clue": "Spiny sea creature" },
|
133 |
-
{ "word": "CORAL", "clue": "Marine organism that builds reefs" },
|
134 |
-
{ "word": "SPONGE", "clue": "Simple marine animal" },
|
135 |
-
{ "word": "OYSTER", "clue": "Pearl-producing mollusk" },
|
136 |
-
{ "word": "CLAM", "clue": "Burrowing shellfish" },
|
137 |
-
{ "word": "MUSSEL", "clue": "Dark-shelled mollusk" },
|
138 |
-
{ "word": "SNAIL", "clue": "Spiral-shelled gastropod" },
|
139 |
-
{ "word": "SLUG", "clue": "Shell-less gastropod" },
|
140 |
-
{ "word": "WORM", "clue": "Segmented invertebrate" },
|
141 |
-
{ "word": "SPIDER", "clue": "Eight-legged web spinner" },
|
142 |
-
{ "word": "SCORPION", "clue": "Arachnid with stinging tail" },
|
143 |
-
{ "word": "ANT", "clue": "Social insect worker" },
|
144 |
-
{ "word": "BEE", "clue": "Honey-making insect" },
|
145 |
-
{ "word": "WASP", "clue": "Stinging flying insect" },
|
146 |
-
{ "word": "HORNET", "clue": "Large aggressive wasp" },
|
147 |
-
{ "word": "FLY", "clue": "Common buzzing insect" },
|
148 |
-
{ "word": "MOSQUITO", "clue": "Blood-sucking insect" },
|
149 |
-
{ "word": "BEETLE", "clue": "Hard-shelled insect" },
|
150 |
-
{ "word": "LADYBUG", "clue": "Red spotted beneficial insect" },
|
151 |
-
{ "word": "DRAGONFLY", "clue": "Large-winged flying insect" },
|
152 |
-
{ "word": "GRASSHOPPER", "clue": "Jumping green insect" },
|
153 |
-
{ "word": "CRICKET", "clue": "Chirping insect" },
|
154 |
-
{ "word": "MANTIS", "clue": "Praying insect predator" },
|
155 |
-
{ "word": "MOTH", "clue": "Nocturnal butterfly relative" },
|
156 |
-
{ "word": "CATERPILLAR", "clue": "Butterfly larva" },
|
157 |
-
{ "word": "COCOON", "clue": "Insect transformation casing" },
|
158 |
-
{ "word": "TERMITE", "clue": "Wood-eating social insect" },
|
159 |
-
{ "word": "TICK", "clue": "Blood-sucking parasite" },
|
160 |
-
{ "word": "FLEA", "clue": "Jumping parasite" },
|
161 |
-
{ "word": "LOUSE", "clue": "Small parasitic insect" },
|
162 |
-
{ "word": "APHID", "clue": "Plant-sucking insect" },
|
163 |
-
{ "word": "MAGGOT", "clue": "Fly larva" },
|
164 |
-
{ "word": "GRUB", "clue": "Beetle larva" }
|
165 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/data/word-lists/geography.json
DELETED
@@ -1,161 +0,0 @@
|
|
1 |
-
[
|
2 |
-
{ "word": "MOUNTAIN", "clue": "High elevation landform" },
|
3 |
-
{ "word": "OCEAN", "clue": "Large body of salt water" },
|
4 |
-
{ "word": "DESERT", "clue": "Dry, arid region" },
|
5 |
-
{ "word": "CONTINENT", "clue": "Large landmass" },
|
6 |
-
{ "word": "RIVER", "clue": "Flowing body of water" },
|
7 |
-
{ "word": "ISLAND", "clue": "Land surrounded by water" },
|
8 |
-
{ "word": "FOREST", "clue": "Dense area of trees" },
|
9 |
-
{ "word": "VALLEY", "clue": "Low area between hills" },
|
10 |
-
{ "word": "LAKE", "clue": "Body of freshwater" },
|
11 |
-
{ "word": "BEACH", "clue": "Sandy shore by water" },
|
12 |
-
{ "word": "CLIFF", "clue": "Steep rock face" },
|
13 |
-
{ "word": "PLATEAU", "clue": "Elevated flat area" },
|
14 |
-
{ "word": "CANYON", "clue": "Deep gorge with steep sides" },
|
15 |
-
{ "word": "GLACIER", "clue": "Moving mass of ice" },
|
16 |
-
{ "word": "VOLCANO", "clue": "Mountain that erupts" },
|
17 |
-
{ "word": "PENINSULA", "clue": "Land surrounded by water on three sides" },
|
18 |
-
{ "word": "ARCHIPELAGO", "clue": "Group of islands" },
|
19 |
-
{ "word": "PRAIRIE", "clue": "Grassland plain" },
|
20 |
-
{ "word": "TUNDRA", "clue": "Cold, treeless region" },
|
21 |
-
{ "word": "SAVANNA", "clue": "Tropical grassland" },
|
22 |
-
{ "word": "EQUATOR", "clue": "Earth's middle line" },
|
23 |
-
{ "word": "LATITUDE", "clue": "Distance from equator" },
|
24 |
-
{ "word": "LONGITUDE", "clue": "Distance from prime meridian" },
|
25 |
-
{ "word": "CLIMATE", "clue": "Long-term weather pattern" },
|
26 |
-
{ "word": "MONSOON", "clue": "Seasonal wind pattern" },
|
27 |
-
{ "word": "CAPITAL", "clue": "Main city of country" },
|
28 |
-
{ "word": "BORDER", "clue": "Boundary between countries" },
|
29 |
-
{ "word": "COAST", "clue": "Land meeting the sea" },
|
30 |
-
{ "word": "STRAIT", "clue": "Narrow water passage" },
|
31 |
-
{ "word": "DELTA", "clue": "River mouth formation" },
|
32 |
-
{ "word": "FJORD", "clue": "Narrow inlet between cliffs" },
|
33 |
-
{ "word": "ATOLL", "clue": "Ring-shaped coral island" },
|
34 |
-
{ "word": "MESA", "clue": "Flat-topped hill" },
|
35 |
-
{ "word": "BUTTE", "clue": "Isolated hill with steep sides" },
|
36 |
-
{ "word": "GORGE", "clue": "Deep narrow valley" },
|
37 |
-
{ "word": "RAVINE", "clue": "Small narrow gorge" },
|
38 |
-
{ "word": "RIDGE", "clue": "Long narrow hilltop" },
|
39 |
-
{ "word": "PEAK", "clue": "Mountain summit" },
|
40 |
-
{ "word": "SUMMIT", "clue": "Highest point" },
|
41 |
-
{ "word": "FOOTHILLS", "clue": "Hills at base of mountains" },
|
42 |
-
{ "word": "RANGE", "clue": "Chain of mountains" },
|
43 |
-
{ "word": "BASIN", "clue": "Low-lying area" },
|
44 |
-
{ "word": "WATERSHED", "clue": "Drainage area" },
|
45 |
-
{ "word": "ESTUARY", "clue": "Where river meets sea" },
|
46 |
-
{ "word": "BAY", "clue": "Curved inlet of water" },
|
47 |
-
{ "word": "GULF", "clue": "Large bay" },
|
48 |
-
{ "word": "CAPE", "clue": "Point of land into water" },
|
49 |
-
{ "word": "HEADLAND", "clue": "High point of land" },
|
50 |
-
{ "word": "LAGOON", "clue": "Shallow coastal body of water" },
|
51 |
-
{ "word": "REEF", "clue": "Underwater rock formation" },
|
52 |
-
{ "word": "SHOAL", "clue": "Shallow area in water" },
|
53 |
-
{ "word": "CHANNEL", "clue": "Deep water passage" },
|
54 |
-
{ "word": "SOUND", "clue": "Large sea inlet" },
|
55 |
-
{ "word": "HARBOR", "clue": "Sheltered port area" },
|
56 |
-
{ "word": "INLET", "clue": "Small bay" },
|
57 |
-
{ "word": "COVE", "clue": "Small sheltered bay" },
|
58 |
-
{ "word": "MARSH", "clue": "Wetland area" },
|
59 |
-
{ "word": "SWAMP", "clue": "Forested wetland" },
|
60 |
-
{ "word": "BOG", "clue": "Acidic wetland" },
|
61 |
-
{ "word": "OASIS", "clue": "Fertile spot in desert" },
|
62 |
-
{ "word": "DUNE", "clue": "Sand hill" },
|
63 |
-
{ "word": "PLAIN", "clue": "Flat grassland" },
|
64 |
-
{ "word": "STEPPE", "clue": "Dry grassland" },
|
65 |
-
{ "word": "TAIGA", "clue": "Northern coniferous forest" },
|
66 |
-
{ "word": "RAINFOREST", "clue": "Dense tropical forest" },
|
67 |
-
{ "word": "JUNGLE", "clue": "Dense tropical vegetation" },
|
68 |
-
{ "word": "WOODLAND", "clue": "Area with scattered trees" },
|
69 |
-
{ "word": "GROVE", "clue": "Small group of trees" },
|
70 |
-
{ "word": "MEADOW", "clue": "Grassy field" },
|
71 |
-
{ "word": "PASTURE", "clue": "Grazing land" },
|
72 |
-
{ "word": "FIELD", "clue": "Open area of land" },
|
73 |
-
{ "word": "MOOR", "clue": "Open uncultivated land" },
|
74 |
-
{ "word": "HEATH", "clue": "Shrubland area" },
|
75 |
-
{ "word": "ARCTIC", "clue": "Cold northern region" },
|
76 |
-
{ "word": "ANTARCTIC", "clue": "Cold southern region" },
|
77 |
-
{ "word": "POLAR", "clue": "Of the poles" },
|
78 |
-
{ "word": "TROPICAL", "clue": "Hot humid climate zone" },
|
79 |
-
{ "word": "TEMPERATE", "clue": "Moderate climate zone" },
|
80 |
-
{ "word": "ARID", "clue": "Very dry" },
|
81 |
-
{ "word": "HUMID", "clue": "Moist air" },
|
82 |
-
{ "word": "ALTITUDE", "clue": "Height above sea level" },
|
83 |
-
{ "word": "ELEVATION", "clue": "Height of land" },
|
84 |
-
{ "word": "TERRAIN", "clue": "Physical features of land" },
|
85 |
-
{ "word": "TOPOGRAPHY", "clue": "Surface features of area" },
|
86 |
-
{ "word": "GEOGRAPHY", "clue": "Study of Earth's features" },
|
87 |
-
{ "word": "CARTOGRAPHY", "clue": "Map making" },
|
88 |
-
{ "word": "MERIDIAN", "clue": "Longitude line" },
|
89 |
-
{ "word": "PARALLEL", "clue": "Latitude line" },
|
90 |
-
{ "word": "HEMISPHERE", "clue": "Half of Earth" },
|
91 |
-
{ "word": "TROPICS", "clue": "Hot climate zone" },
|
92 |
-
{ "word": "POLES", "clue": "Earth's endpoints" },
|
93 |
-
{ "word": "AXIS", "clue": "Earth's rotation line" },
|
94 |
-
{ "word": "ORBIT", "clue": "Path around sun" },
|
95 |
-
{ "word": "SEASON", "clue": "Time of year" },
|
96 |
-
{ "word": "SOLSTICE", "clue": "Longest or shortest day" },
|
97 |
-
{ "word": "EQUINOX", "clue": "Equal day and night" },
|
98 |
-
{ "word": "COMPASS", "clue": "Direction-finding tool" },
|
99 |
-
{ "word": "NAVIGATION", "clue": "Finding your way" },
|
100 |
-
{ "word": "BEARING", "clue": "Direction or course" },
|
101 |
-
{ "word": "AZIMUTH", "clue": "Compass direction" },
|
102 |
-
{ "word": "SCALE", "clue": "Map size ratio" },
|
103 |
-
{ "word": "LEGEND", "clue": "Map symbol key" },
|
104 |
-
{ "word": "CONTOUR", "clue": "Elevation line on map" },
|
105 |
-
{ "word": "GRID", "clue": "Map reference system" },
|
106 |
-
{ "word": "PROJECTION", "clue": "Map flattening method" },
|
107 |
-
{ "word": "SURVEY", "clue": "Land measurement" },
|
108 |
-
{ "word": "BOUNDARY", "clue": "Dividing line" },
|
109 |
-
{ "word": "FRONTIER", "clue": "Border region" },
|
110 |
-
{ "word": "TERRITORY", "clue": "Area of land" },
|
111 |
-
{ "word": "REGION", "clue": "Geographic area" },
|
112 |
-
{ "word": "ZONE", "clue": "Designated area" },
|
113 |
-
{ "word": "DISTRICT", "clue": "Administrative area" },
|
114 |
-
{ "word": "PROVINCE", "clue": "Political subdivision" },
|
115 |
-
{ "word": "STATE", "clue": "Political entity" },
|
116 |
-
{ "word": "COUNTY", "clue": "Local government area" },
|
117 |
-
{ "word": "CITY", "clue": "Large urban area" },
|
118 |
-
{ "word": "TOWN", "clue": "Small urban area" },
|
119 |
-
{ "word": "VILLAGE", "clue": "Small rural community" },
|
120 |
-
{ "word": "HAMLET", "clue": "Very small village" },
|
121 |
-
{ "word": "SUBURB", "clue": "Residential area outside city" },
|
122 |
-
{ "word": "URBAN", "clue": "City-like" },
|
123 |
-
{ "word": "RURAL", "clue": "Countryside" },
|
124 |
-
{ "word": "METROPOLITAN", "clue": "Large city area" },
|
125 |
-
{ "word": "POPULATION", "clue": "Number of people" },
|
126 |
-
{ "word": "DENSITY", "clue": "Crowdedness" },
|
127 |
-
{ "word": "SETTLEMENT", "clue": "Place where people live" },
|
128 |
-
{ "word": "COLONY", "clue": "Overseas territory" },
|
129 |
-
{ "word": "NATION", "clue": "Country" },
|
130 |
-
{ "word": "REPUBLIC", "clue": "Democratic state" },
|
131 |
-
{ "word": "KINGDOM", "clue": "Monarchy" },
|
132 |
-
{ "word": "EMPIRE", "clue": "Large political entity" },
|
133 |
-
{ "word": "FEDERATION", "clue": "Union of states" },
|
134 |
-
{ "word": "ALLIANCE", "clue": "Partnership of nations" },
|
135 |
-
{ "word": "TREATY", "clue": "International agreement" },
|
136 |
-
{ "word": "TRADE", "clue": "Commercial exchange" },
|
137 |
-
{ "word": "EXPORT", "clue": "Goods sent abroad" },
|
138 |
-
{ "word": "IMPORT", "clue": "Goods brought in" },
|
139 |
-
{ "word": "COMMERCE", "clue": "Business activity" },
|
140 |
-
{ "word": "INDUSTRY", "clue": "Manufacturing" },
|
141 |
-
{ "word": "AGRICULTURE", "clue": "Farming" },
|
142 |
-
{ "word": "MINING", "clue": "Extracting minerals" },
|
143 |
-
{ "word": "FORESTRY", "clue": "Tree management" },
|
144 |
-
{ "word": "FISHING", "clue": "Catching fish" },
|
145 |
-
{ "word": "TOURISM", "clue": "Travel industry" },
|
146 |
-
{ "word": "TRANSPORTATION", "clue": "Moving people and goods" },
|
147 |
-
{ "word": "INFRASTRUCTURE", "clue": "Basic facilities" },
|
148 |
-
{ "word": "COMMUNICATION", "clue": "Information exchange" },
|
149 |
-
{ "word": "CULTURE", "clue": "Way of life" },
|
150 |
-
{ "word": "LANGUAGE", "clue": "Communication system" },
|
151 |
-
{ "word": "RELIGION", "clue": "Belief system" },
|
152 |
-
{ "word": "ETHNICITY", "clue": "Cultural group" },
|
153 |
-
{ "word": "MIGRATION", "clue": "Movement of people" },
|
154 |
-
{ "word": "IMMIGRATION", "clue": "Moving into country" },
|
155 |
-
{ "word": "EMIGRATION", "clue": "Moving out of country" },
|
156 |
-
{ "word": "DIASPORA", "clue": "Scattered population" },
|
157 |
-
{ "word": "NOMAD", "clue": "Wandering person" },
|
158 |
-
{ "word": "REFUGEE", "clue": "Displaced person" },
|
159 |
-
{ "word": "CENSUS", "clue": "Population count" },
|
160 |
-
{ "word": "DEMOGRAPHIC", "clue": "Population characteristic" }
|
161 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/data/word-lists/science.json
DELETED
@@ -1,170 +0,0 @@
|
|
1 |
-
[
|
2 |
-
{ "word": "ATOM", "clue": "Smallest unit of matter" },
|
3 |
-
{ "word": "GRAVITY", "clue": "Force that pulls objects down" },
|
4 |
-
{ "word": "MOLECULE", "clue": "Group of atoms bonded together" },
|
5 |
-
{ "word": "PHOTON", "clue": "Particle of light" },
|
6 |
-
{ "word": "CHEMISTRY", "clue": "Study of matter and reactions" },
|
7 |
-
{ "word": "PHYSICS", "clue": "Study of matter and energy" },
|
8 |
-
{ "word": "BIOLOGY", "clue": "Study of living organisms" },
|
9 |
-
{ "word": "ELEMENT", "clue": "Pure chemical substance" },
|
10 |
-
{ "word": "OXYGEN", "clue": "Gas essential for breathing" },
|
11 |
-
{ "word": "CARBON", "clue": "Element found in all life" },
|
12 |
-
{ "word": "HYDROGEN", "clue": "Lightest chemical element" },
|
13 |
-
{ "word": "ENERGY", "clue": "Capacity to do work" },
|
14 |
-
{ "word": "FORCE", "clue": "Push or pull on an object" },
|
15 |
-
{ "word": "VELOCITY", "clue": "Speed with direction" },
|
16 |
-
{ "word": "MASS", "clue": "Amount of matter in object" },
|
17 |
-
{ "word": "VOLUME", "clue": "Amount of space occupied" },
|
18 |
-
{ "word": "DENSITY", "clue": "Mass per unit volume" },
|
19 |
-
{ "word": "PRESSURE", "clue": "Force per unit area" },
|
20 |
-
{ "word": "TEMPERATURE", "clue": "Measure of heat" },
|
21 |
-
{ "word": "ELECTRON", "clue": "Negatively charged particle" },
|
22 |
-
{ "word": "PROTON", "clue": "Positively charged particle" },
|
23 |
-
{ "word": "NEUTRON", "clue": "Neutral atomic particle" },
|
24 |
-
{ "word": "NUCLEUS", "clue": "Center of an atom" },
|
25 |
-
{ "word": "CELL", "clue": "Basic unit of life" },
|
26 |
-
{ "word": "DNA", "clue": "Genetic blueprint molecule" },
|
27 |
-
{ "word": "PROTEIN", "clue": "Complex biological molecule" },
|
28 |
-
{ "word": "ENZYME", "clue": "Biological catalyst" },
|
29 |
-
{ "word": "VIRUS", "clue": "Infectious agent" },
|
30 |
-
{ "word": "BACTERIA", "clue": "Single-celled organisms" },
|
31 |
-
{ "word": "EVOLUTION", "clue": "Change in species over time" },
|
32 |
-
{ "word": "ISOTOPE", "clue": "Atom variant with different neutrons" },
|
33 |
-
{ "word": "ION", "clue": "Charged atom or molecule" },
|
34 |
-
{ "word": "COMPOUND", "clue": "Chemical combination of elements" },
|
35 |
-
{ "word": "MIXTURE", "clue": "Combined substances retaining properties" },
|
36 |
-
{ "word": "SOLUTION", "clue": "Dissolved mixture" },
|
37 |
-
{ "word": "ACID", "clue": "Sour chemical with low pH" },
|
38 |
-
{ "word": "BASE", "clue": "Alkaline substance with high pH" },
|
39 |
-
{ "word": "SALT", "clue": "Ionic compound from acid-base reaction" },
|
40 |
-
{ "word": "CATALYST", "clue": "Substance that speeds reactions" },
|
41 |
-
{ "word": "RNA", "clue": "Genetic messenger molecule" },
|
42 |
-
{ "word": "GENE", "clue": "Heredity unit on chromosome" },
|
43 |
-
{ "word": "CHROMOSOME", "clue": "Gene-carrying structure" },
|
44 |
-
{ "word": "TISSUE", "clue": "Group of similar cells" },
|
45 |
-
{ "word": "ORGAN", "clue": "Body part with specific function" },
|
46 |
-
{ "word": "SYSTEM", "clue": "Group of organs working together" },
|
47 |
-
{ "word": "ORGANISM", "clue": "Living individual entity" },
|
48 |
-
{ "word": "SPECIES", "clue": "Group of similar organisms" },
|
49 |
-
{ "word": "ADAPTATION", "clue": "Survival-enhancing change" },
|
50 |
-
{ "word": "MUTATION", "clue": "Genetic change in DNA" },
|
51 |
-
{ "word": "HEREDITY", "clue": "Passing traits to offspring" },
|
52 |
-
{ "word": "ECOSYSTEM", "clue": "Community and environment" },
|
53 |
-
{ "word": "HABITAT", "clue": "Natural living environment" },
|
54 |
-
{ "word": "BIODIVERSITY", "clue": "Variety of life forms" },
|
55 |
-
{ "word": "PHOTOSYNTHESIS", "clue": "Plant energy-making process" },
|
56 |
-
{ "word": "RESPIRATION", "clue": "Cellular breathing process" },
|
57 |
-
{ "word": "METABOLISM", "clue": "Chemical processes in body" },
|
58 |
-
{ "word": "HOMEOSTASIS", "clue": "Body's internal balance" },
|
59 |
-
{ "word": "MITOSIS", "clue": "Cell division for growth" },
|
60 |
-
{ "word": "MEIOSIS", "clue": "Cell division for reproduction" },
|
61 |
-
{ "word": "EMBRYO", "clue": "Early development stage" },
|
62 |
-
{ "word": "FOSSIL", "clue": "Preserved ancient remains" },
|
63 |
-
{ "word": "GEOLOGY", "clue": "Study of Earth's structure" },
|
64 |
-
{ "word": "MINERAL", "clue": "Natural inorganic crystal" },
|
65 |
-
{ "word": "ROCK", "clue": "Solid earth material" },
|
66 |
-
{ "word": "SEDIMENT", "clue": "Settled particles" },
|
67 |
-
{ "word": "EROSION", "clue": "Gradual wearing away" },
|
68 |
-
{ "word": "VOLCANO", "clue": "Earth opening spewing lava" },
|
69 |
-
{ "word": "EARTHQUAKE", "clue": "Ground shaking from plate movement" },
|
70 |
-
{ "word": "PLATE", "clue": "Earth's crust section" },
|
71 |
-
{ "word": "MAGMA", "clue": "Molten rock beneath surface" },
|
72 |
-
{ "word": "LAVA", "clue": "Molten rock on surface" },
|
73 |
-
{ "word": "CRYSTAL", "clue": "Ordered atomic structure" },
|
74 |
-
{ "word": "ATMOSPHERE", "clue": "Layer of gases around Earth" },
|
75 |
-
{ "word": "CLIMATE", "clue": "Long-term weather pattern" },
|
76 |
-
{ "word": "WEATHER", "clue": "Short-term atmospheric conditions" },
|
77 |
-
{ "word": "PRECIPITATION", "clue": "Water falling from clouds" },
|
78 |
-
{ "word": "HUMIDITY", "clue": "Moisture in air" },
|
79 |
-
{ "word": "WIND", "clue": "Moving air mass" },
|
80 |
-
{ "word": "STORM", "clue": "Violent weather event" },
|
81 |
-
{ "word": "HURRICANE", "clue": "Powerful tropical cyclone" },
|
82 |
-
{ "word": "TORNADO", "clue": "Rotating column of air" },
|
83 |
-
{ "word": "LIGHTNING", "clue": "Electrical discharge in sky" },
|
84 |
-
{ "word": "THUNDER", "clue": "Sound of lightning" },
|
85 |
-
{ "word": "RAINBOW", "clue": "Spectrum of light in sky" },
|
86 |
-
{ "word": "ASTRONOMY", "clue": "Study of celestial objects" },
|
87 |
-
{ "word": "GALAXY", "clue": "Collection of stars and planets" },
|
88 |
-
{ "word": "PLANET", "clue": "Large orbiting celestial body" },
|
89 |
-
{ "word": "STAR", "clue": "Self-luminous celestial body" },
|
90 |
-
{ "word": "MOON", "clue": "Natural satellite of planet" },
|
91 |
-
{ "word": "COMET", "clue": "Icy body with tail" },
|
92 |
-
{ "word": "ASTEROID", "clue": "Rocky space object" },
|
93 |
-
{ "word": "METEOR", "clue": "Space rock entering atmosphere" },
|
94 |
-
{ "word": "ORBIT", "clue": "Curved path around object" },
|
95 |
-
{ "word": "LIGHT", "clue": "Electromagnetic radiation" },
|
96 |
-
{ "word": "SPECTRUM", "clue": "Range of electromagnetic radiation" },
|
97 |
-
{ "word": "WAVELENGTH", "clue": "Distance between wave peaks" },
|
98 |
-
{ "word": "FREQUENCY", "clue": "Waves per unit time" },
|
99 |
-
{ "word": "AMPLITUDE", "clue": "Wave height or intensity" },
|
100 |
-
{ "word": "SOUND", "clue": "Vibrations in air" },
|
101 |
-
{ "word": "ECHO", "clue": "Reflected sound" },
|
102 |
-
{ "word": "RESONANCE", "clue": "Vibration amplification" },
|
103 |
-
{ "word": "DOPPLER", "clue": "Wave frequency shift effect" },
|
104 |
-
{ "word": "MOTION", "clue": "Change in position" },
|
105 |
-
{ "word": "ACCELERATION", "clue": "Change in velocity" },
|
106 |
-
{ "word": "MOMENTUM", "clue": "Mass times velocity" },
|
107 |
-
{ "word": "INERTIA", "clue": "Resistance to motion change" },
|
108 |
-
{ "word": "FRICTION", "clue": "Resistance to sliding" },
|
109 |
-
{ "word": "HEAT", "clue": "Thermal energy transfer" },
|
110 |
-
{ "word": "COMBUSTION", "clue": "Burning chemical reaction" },
|
111 |
-
{ "word": "OXIDATION", "clue": "Reaction with oxygen" },
|
112 |
-
{ "word": "REDUCTION", "clue": "Gain of electrons" },
|
113 |
-
{ "word": "ELECTROLYSIS", "clue": "Chemical breakdown by electricity" },
|
114 |
-
{ "word": "CONDUCTIVITY", "clue": "Ability to transfer energy" },
|
115 |
-
{ "word": "INSULATOR", "clue": "Material blocking energy flow" },
|
116 |
-
{ "word": "SEMICONDUCTOR", "clue": "Partial electrical conductor" },
|
117 |
-
{ "word": "MAGNETISM", "clue": "Force of magnetic attraction" },
|
118 |
-
{ "word": "FIELD", "clue": "Region of force influence" },
|
119 |
-
{ "word": "CIRCUIT", "clue": "Closed electrical path" },
|
120 |
-
{ "word": "CURRENT", "clue": "Flow of electric charge" },
|
121 |
-
{ "word": "VOLTAGE", "clue": "Electric potential difference" },
|
122 |
-
{ "word": "RESISTANCE", "clue": "Opposition to current flow" },
|
123 |
-
{ "word": "CAPACITOR", "clue": "Device storing electric charge" },
|
124 |
-
{ "word": "INDUCTOR", "clue": "Device storing magnetic energy" },
|
125 |
-
{ "word": "TRANSISTOR", "clue": "Electronic switching device" },
|
126 |
-
{ "word": "LASER", "clue": "Focused beam of light" },
|
127 |
-
{ "word": "RADAR", "clue": "Radio detection system" },
|
128 |
-
{ "word": "SONAR", "clue": "Sound detection system" },
|
129 |
-
{ "word": "TELESCOPE", "clue": "Instrument for viewing distant objects" },
|
130 |
-
{ "word": "MICROSCOPE", "clue": "Instrument for viewing small objects" },
|
131 |
-
{ "word": "HYPOTHESIS", "clue": "Testable scientific prediction" },
|
132 |
-
{ "word": "THEORY", "clue": "Well-tested scientific explanation" },
|
133 |
-
{ "word": "LAW", "clue": "Consistently observed scientific rule" },
|
134 |
-
{ "word": "EXPERIMENT", "clue": "Controlled scientific test" },
|
135 |
-
{ "word": "OBSERVATION", "clue": "Careful scientific watching" },
|
136 |
-
{ "word": "MEASUREMENT", "clue": "Quantified observation" },
|
137 |
-
{ "word": "ANALYSIS", "clue": "Detailed examination of data" },
|
138 |
-
{ "word": "SYNTHESIS", "clue": "Combining elements into whole" },
|
139 |
-
{ "word": "VARIABLE", "clue": "Factor that can change" },
|
140 |
-
{ "word": "CONTROL", "clue": "Unchanged comparison group" },
|
141 |
-
{ "word": "DATA", "clue": "Information collected from tests" },
|
142 |
-
{ "word": "STATISTICS", "clue": "Mathematical analysis of data" },
|
143 |
-
{ "word": "PROBABILITY", "clue": "Likelihood of occurrence" },
|
144 |
-
{ "word": "PRECISION", "clue": "Exactness of measurement" },
|
145 |
-
{ "word": "ACCURACY", "clue": "Correctness of measurement" },
|
146 |
-
{ "word": "ERROR", "clue": "Difference from true value" },
|
147 |
-
{ "word": "UNCERTAINTY", "clue": "Range of doubt in measurement" },
|
148 |
-
{ "word": "CALIBRATION", "clue": "Adjusting instrument accuracy" },
|
149 |
-
{ "word": "STANDARD", "clue": "Reference for measurement" },
|
150 |
-
{ "word": "UNIT", "clue": "Base measure of quantity" },
|
151 |
-
{ "word": "METRIC", "clue": "Decimal measurement system" },
|
152 |
-
{ "word": "WEIGHT", "clue": "Force of gravity on mass" },
|
153 |
-
{ "word": "CONCENTRATION", "clue": "Amount of substance per volume" },
|
154 |
-
{ "word": "MOLARITY", "clue": "Moles of solute per liter" },
|
155 |
-
{ "word": "EQUILIBRIUM", "clue": "State of balanced forces" },
|
156 |
-
{ "word": "STABILITY", "clue": "Resistance to change" },
|
157 |
-
{ "word": "DECAY", "clue": "Gradual breakdown process" },
|
158 |
-
{ "word": "RADIATION", "clue": "Energy emitted from source" },
|
159 |
-
{ "word": "RADIOACTIVE", "clue": "Emitting nuclear radiation" },
|
160 |
-
{ "word": "HALFLIFE", "clue": "Time for half to decay" },
|
161 |
-
{ "word": "FUSION", "clue": "Nuclear combining reaction" },
|
162 |
-
{ "word": "FISSION", "clue": "Nuclear splitting reaction" },
|
163 |
-
{ "word": "QUANTUM", "clue": "Discrete packet of energy" },
|
164 |
-
{ "word": "PARTICLE", "clue": "Tiny piece of matter" },
|
165 |
-
{ "word": "WAVE", "clue": "Energy transfer disturbance" },
|
166 |
-
{ "word": "INTERFERENCE", "clue": "Wave interaction effect" },
|
167 |
-
{ "word": "DIFFRACTION", "clue": "Wave bending around obstacle" },
|
168 |
-
{ "word": "REFLECTION", "clue": "Bouncing back of waves" },
|
169 |
-
{ "word": "REFRACTION", "clue": "Bending of waves through medium" }
|
170 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/data/word-lists/technology.json
DELETED
@@ -1,221 +0,0 @@
|
|
1 |
-
[
|
2 |
-
{ "word": "COMPUTER", "clue": "Electronic processing device" },
|
3 |
-
{ "word": "INTERNET", "clue": "Global computer network" },
|
4 |
-
{ "word": "ALGORITHM", "clue": "Set of rules for solving problems" },
|
5 |
-
{ "word": "DATABASE", "clue": "Organized collection of data" },
|
6 |
-
{ "word": "SOFTWARE", "clue": "Computer programs" },
|
7 |
-
{ "word": "HARDWARE", "clue": "Physical computer components" },
|
8 |
-
{ "word": "NETWORK", "clue": "Connected system of computers" },
|
9 |
-
{ "word": "CODE", "clue": "Programming instructions" },
|
10 |
-
{ "word": "ROBOT", "clue": "Automated machine" },
|
11 |
-
{ "word": "ARTIFICIAL", "clue": "Made by humans, not natural" },
|
12 |
-
{ "word": "DIGITAL", "clue": "Using binary data" },
|
13 |
-
{ "word": "BINARY", "clue": "Base-2 number system" },
|
14 |
-
{ "word": "PROCESSOR", "clue": "Computer's brain" },
|
15 |
-
{ "word": "MEMORY", "clue": "Data storage component" },
|
16 |
-
{ "word": "KEYBOARD", "clue": "Input device with keys" },
|
17 |
-
{ "word": "MONITOR", "clue": "Computer display screen" },
|
18 |
-
{ "word": "MOUSE", "clue": "Pointing input device" },
|
19 |
-
{ "word": "PRINTER", "clue": "Device that prints documents" },
|
20 |
-
{ "word": "SCANNER", "clue": "Device that digitizes images" },
|
21 |
-
{ "word": "CAMERA", "clue": "Device that captures images" },
|
22 |
-
{ "word": "SMARTPHONE", "clue": "Portable computing device" },
|
23 |
-
{ "word": "TABLET", "clue": "Touchscreen computing device" },
|
24 |
-
{ "word": "LAPTOP", "clue": "Portable computer" },
|
25 |
-
{ "word": "SERVER", "clue": "Computer that serves data" },
|
26 |
-
{ "word": "CLOUD", "clue": "Internet-based computing" },
|
27 |
-
{ "word": "WEBSITE", "clue": "Collection of web pages" },
|
28 |
-
{ "word": "EMAIL", "clue": "Electronic mail" },
|
29 |
-
{ "word": "BROWSER", "clue": "Web navigation software" },
|
30 |
-
{ "word": "SEARCH", "clue": "Look for information" },
|
31 |
-
{ "word": "DOWNLOAD", "clue": "Transfer data to device" },
|
32 |
-
{ "word": "UPLOAD", "clue": "Transfer data from device" },
|
33 |
-
{ "word": "BANDWIDTH", "clue": "Data transfer capacity" },
|
34 |
-
{ "word": "PROTOCOL", "clue": "Communication rules" },
|
35 |
-
{ "word": "FIREWALL", "clue": "Network security barrier" },
|
36 |
-
{ "word": "ENCRYPTION", "clue": "Data scrambling for security" },
|
37 |
-
{ "word": "PASSWORD", "clue": "Secret access code" },
|
38 |
-
{ "word": "SECURITY", "clue": "Protection from threats" },
|
39 |
-
{ "word": "VIRUS", "clue": "Malicious computer program" },
|
40 |
-
{ "word": "MALWARE", "clue": "Harmful software" },
|
41 |
-
{ "word": "ANTIVIRUS", "clue": "Protection software" },
|
42 |
-
{ "word": "BACKUP", "clue": "Data safety copy" },
|
43 |
-
{ "word": "RECOVERY", "clue": "Data restoration process" },
|
44 |
-
{ "word": "STORAGE", "clue": "Data keeping capacity" },
|
45 |
-
{ "word": "HARDDRIVE", "clue": "Magnetic storage device" },
|
46 |
-
{ "word": "FLASH", "clue": "Solid state storage" },
|
47 |
-
{ "word": "RAM", "clue": "Random access memory" },
|
48 |
-
{ "word": "ROM", "clue": "Read-only memory" },
|
49 |
-
{ "word": "CPU", "clue": "Central processing unit" },
|
50 |
-
{ "word": "GPU", "clue": "Graphics processing unit" },
|
51 |
-
{ "word": "MOTHERBOARD", "clue": "Main circuit board" },
|
52 |
-
{ "word": "CHIP", "clue": "Integrated circuit" },
|
53 |
-
{ "word": "CIRCUIT", "clue": "Electronic pathway" },
|
54 |
-
{ "word": "TRANSISTOR", "clue": "Electronic switch" },
|
55 |
-
{ "word": "SILICON", "clue": "Semiconductor material" },
|
56 |
-
{ "word": "NANOTECHNOLOGY", "clue": "Extremely small scale tech" },
|
57 |
-
{ "word": "AUTOMATION", "clue": "Self-operating technology" },
|
58 |
-
{ "word": "MACHINE", "clue": "Mechanical device" },
|
59 |
-
{ "word": "SENSOR", "clue": "Detection device" },
|
60 |
-
{ "word": "ACTUATOR", "clue": "Movement device" },
|
61 |
-
{ "word": "FEEDBACK", "clue": "System response information" },
|
62 |
-
{ "word": "PROGRAMMING", "clue": "Writing computer instructions" },
|
63 |
-
{ "word": "FUNCTION", "clue": "Reusable code block" },
|
64 |
-
{ "word": "VARIABLE", "clue": "Data storage container" },
|
65 |
-
{ "word": "LOOP", "clue": "Repeating code structure" },
|
66 |
-
{ "word": "CONDITION", "clue": "Decision-making logic" },
|
67 |
-
{ "word": "DEBUG", "clue": "Find and fix errors" },
|
68 |
-
{ "word": "COMPILE", "clue": "Convert code to executable" },
|
69 |
-
{ "word": "RUNTIME", "clue": "Program execution time" },
|
70 |
-
{ "word": "API", "clue": "Application programming interface" },
|
71 |
-
{ "word": "FRAMEWORK", "clue": "Code structure foundation" },
|
72 |
-
{ "word": "LIBRARY", "clue": "Reusable code collection" },
|
73 |
-
{ "word": "MODULE", "clue": "Self-contained code unit" },
|
74 |
-
{ "word": "OBJECT", "clue": "Data and methods container" },
|
75 |
-
{ "word": "CLASS", "clue": "Object blueprint" },
|
76 |
-
{ "word": "INHERITANCE", "clue": "Code reuse mechanism" },
|
77 |
-
{ "word": "INTERFACE", "clue": "System interaction boundary" },
|
78 |
-
{ "word": "PROTOCOL", "clue": "Communication standard" },
|
79 |
-
{ "word": "FORMAT", "clue": "Data structure standard" },
|
80 |
-
{ "word": "SYNTAX", "clue": "Language rules" },
|
81 |
-
{ "word": "SEMANTIC", "clue": "Meaning in code" },
|
82 |
-
{ "word": "PARSING", "clue": "Analyzing code structure" },
|
83 |
-
{ "word": "COMPILER", "clue": "Code translation program" },
|
84 |
-
{ "word": "INTERPRETER", "clue": "Code execution program" },
|
85 |
-
{ "word": "VIRTUAL", "clue": "Simulated environment" },
|
86 |
-
{ "word": "SIMULATION", "clue": "Computer modeling" },
|
87 |
-
{ "word": "EMULATION", "clue": "System imitation" },
|
88 |
-
{ "word": "OPTIMIZATION", "clue": "Performance improvement" },
|
89 |
-
{ "word": "EFFICIENCY", "clue": "Resource usage effectiveness" },
|
90 |
-
{ "word": "PERFORMANCE", "clue": "System speed and quality" },
|
91 |
-
{ "word": "BENCHMARK", "clue": "Performance measurement" },
|
92 |
-
{ "word": "TESTING", "clue": "Quality verification process" },
|
93 |
-
{ "word": "VALIDATION", "clue": "Correctness checking" },
|
94 |
-
{ "word": "VERIFICATION", "clue": "Accuracy confirmation" },
|
95 |
-
{ "word": "QUALITY", "clue": "Standard of excellence" },
|
96 |
-
{ "word": "MAINTENANCE", "clue": "System upkeep" },
|
97 |
-
{ "word": "UPDATE", "clue": "Software improvement" },
|
98 |
-
{ "word": "PATCH", "clue": "Software fix" },
|
99 |
-
{ "word": "VERSION", "clue": "Software release number" },
|
100 |
-
{ "word": "RELEASE", "clue": "Software distribution" },
|
101 |
-
{ "word": "DEPLOYMENT", "clue": "Software installation" },
|
102 |
-
{ "word": "CONFIGURATION", "clue": "System setup" },
|
103 |
-
{ "word": "INSTALLATION", "clue": "Software setup process" },
|
104 |
-
{ "word": "MIGRATION", "clue": "System transition" },
|
105 |
-
{ "word": "INTEGRATION", "clue": "System combination" },
|
106 |
-
{ "word": "COMPATIBILITY", "clue": "System cooperation ability" },
|
107 |
-
{ "word": "INTEROPERABILITY", "clue": "Cross-system communication" },
|
108 |
-
{ "word": "SCALABILITY", "clue": "Growth accommodation ability" },
|
109 |
-
{ "word": "RELIABILITY", "clue": "Consistent performance" },
|
110 |
-
{ "word": "AVAILABILITY", "clue": "System accessibility" },
|
111 |
-
{ "word": "REDUNDANCY", "clue": "Backup system duplication" },
|
112 |
-
{ "word": "FAULT", "clue": "System error condition" },
|
113 |
-
{ "word": "TOLERANCE", "clue": "Error handling ability" },
|
114 |
-
{ "word": "RECOVERY", "clue": "System restoration" },
|
115 |
-
{ "word": "MONITORING", "clue": "System observation" },
|
116 |
-
{ "word": "LOGGING", "clue": "Event recording" },
|
117 |
-
{ "word": "ANALYTICS", "clue": "Data analysis" },
|
118 |
-
{ "word": "METRICS", "clue": "Measurement data" },
|
119 |
-
{ "word": "DASHBOARD", "clue": "Information display panel" },
|
120 |
-
{ "word": "INTERFACE", "clue": "User interaction design" },
|
121 |
-
{ "word": "EXPERIENCE", "clue": "User interaction quality" },
|
122 |
-
{ "word": "USABILITY", "clue": "Ease of use" },
|
123 |
-
{ "word": "ACCESSIBILITY", "clue": "Universal design principle" },
|
124 |
-
{ "word": "RESPONSIVE", "clue": "Adaptive design" },
|
125 |
-
{ "word": "MOBILE", "clue": "Portable device category" },
|
126 |
-
{ "word": "TOUCHSCREEN", "clue": "Touch-sensitive display" },
|
127 |
-
{ "word": "GESTURE", "clue": "Touch movement command" },
|
128 |
-
{ "word": "VOICE", "clue": "Speech interaction" },
|
129 |
-
{ "word": "RECOGNITION", "clue": "Pattern identification" },
|
130 |
-
{ "word": "LEARNING", "clue": "Adaptive improvement" },
|
131 |
-
{ "word": "INTELLIGENCE", "clue": "Artificial reasoning" },
|
132 |
-
{ "word": "NEURAL", "clue": "Brain-inspired network" },
|
133 |
-
{ "word": "DEEP", "clue": "Multi-layered learning" },
|
134 |
-
{ "word": "MACHINE", "clue": "Automated learning system" },
|
135 |
-
{ "word": "DATA", "clue": "Information collection" },
|
136 |
-
{ "word": "BIG", "clue": "Large scale data" },
|
137 |
-
{ "word": "MINING", "clue": "Data pattern extraction" },
|
138 |
-
{ "word": "ANALYSIS", "clue": "Data examination" },
|
139 |
-
{ "word": "VISUALIZATION", "clue": "Data graphic representation" },
|
140 |
-
{ "word": "DASHBOARD", "clue": "Data monitoring panel" },
|
141 |
-
{ "word": "REPORT", "clue": "Data summary document" },
|
142 |
-
{ "word": "QUERY", "clue": "Data search request" },
|
143 |
-
{ "word": "INDEX", "clue": "Data location reference" },
|
144 |
-
{ "word": "SCHEMA", "clue": "Data structure blueprint" },
|
145 |
-
{ "word": "TABLE", "clue": "Data organization structure" },
|
146 |
-
{ "word": "RECORD", "clue": "Data entry" },
|
147 |
-
{ "word": "FIELD", "clue": "Data element" },
|
148 |
-
{ "word": "PRIMARY", "clue": "Main identifier key" },
|
149 |
-
{ "word": "FOREIGN", "clue": "Reference relationship key" },
|
150 |
-
{ "word": "RELATION", "clue": "Data connection" },
|
151 |
-
{ "word": "JOIN", "clue": "Data combination operation" },
|
152 |
-
{ "word": "TRANSACTION", "clue": "Data operation sequence" },
|
153 |
-
{ "word": "COMMIT", "clue": "Data change confirmation" },
|
154 |
-
{ "word": "ROLLBACK", "clue": "Data change reversal" },
|
155 |
-
{ "word": "CONCURRENCY", "clue": "Simultaneous access handling" },
|
156 |
-
{ "word": "LOCK", "clue": "Data access control" },
|
157 |
-
{ "word": "SYNCHRONIZATION", "clue": "Timing coordination" },
|
158 |
-
{ "word": "THREAD", "clue": "Execution sequence" },
|
159 |
-
{ "word": "PROCESS", "clue": "Running program instance" },
|
160 |
-
{ "word": "MULTITASKING", "clue": "Multiple process handling" },
|
161 |
-
{ "word": "PARALLEL", "clue": "Simultaneous execution" },
|
162 |
-
{ "word": "DISTRIBUTED", "clue": "Spread across multiple systems" },
|
163 |
-
{ "word": "CLUSTER", "clue": "Group of connected computers" },
|
164 |
-
{ "word": "GRID", "clue": "Distributed computing network" },
|
165 |
-
{ "word": "PEER", "clue": "Equal network participant" },
|
166 |
-
{ "word": "CLIENT", "clue": "Service requesting system" },
|
167 |
-
{ "word": "SERVICE", "clue": "System functionality provider" },
|
168 |
-
{ "word": "MICROSERVICE", "clue": "Small independent service" },
|
169 |
-
{ "word": "CONTAINER", "clue": "Isolated application environment" },
|
170 |
-
{ "word": "DOCKER", "clue": "Containerization platform" },
|
171 |
-
{ "word": "KUBERNETES", "clue": "Container orchestration" },
|
172 |
-
{ "word": "DEVOPS", "clue": "Development operations practice" },
|
173 |
-
{ "word": "AGILE", "clue": "Flexible development method" },
|
174 |
-
{ "word": "SCRUM", "clue": "Iterative development framework" },
|
175 |
-
{ "word": "SPRINT", "clue": "Short development cycle" },
|
176 |
-
{ "word": "KANBAN", "clue": "Visual workflow management" },
|
177 |
-
{ "word": "CONTINUOUS", "clue": "Ongoing integration practice" },
|
178 |
-
{ "word": "PIPELINE", "clue": "Automated workflow" },
|
179 |
-
{ "word": "BUILD", "clue": "Software compilation process" },
|
180 |
-
{ "word": "TESTING", "clue": "Quality assurance process" },
|
181 |
-
{ "word": "AUTOMATION", "clue": "Manual task elimination" },
|
182 |
-
{ "word": "SCRIPT", "clue": "Automated task sequence" },
|
183 |
-
{ "word": "BATCH", "clue": "Group processing" },
|
184 |
-
{ "word": "STREAMING", "clue": "Continuous data flow" },
|
185 |
-
{ "word": "REALTIME", "clue": "Immediate processing" },
|
186 |
-
{ "word": "LATENCY", "clue": "Response delay time" },
|
187 |
-
{ "word": "THROUGHPUT", "clue": "Processing capacity" },
|
188 |
-
{ "word": "BOTTLENECK", "clue": "Performance limitation point" },
|
189 |
-
{ "word": "CACHE", "clue": "Fast temporary storage" },
|
190 |
-
{ "word": "BUFFER", "clue": "Temporary data holder" },
|
191 |
-
{ "word": "QUEUE", "clue": "Ordered waiting line" },
|
192 |
-
{ "word": "STACK", "clue": "Last-in-first-out structure" },
|
193 |
-
{ "word": "HEAP", "clue": "Dynamic memory area" },
|
194 |
-
{ "word": "POINTER", "clue": "Memory address reference" },
|
195 |
-
{ "word": "REFERENCE", "clue": "Object location indicator" },
|
196 |
-
{ "word": "GARBAGE", "clue": "Unused memory collection" },
|
197 |
-
{ "word": "ALLOCATION", "clue": "Memory assignment" },
|
198 |
-
{ "word": "DEALLOCATION", "clue": "Memory release" },
|
199 |
-
{ "word": "LEAK", "clue": "Memory usage error" },
|
200 |
-
{ "word": "OVERFLOW", "clue": "Capacity exceeding error" },
|
201 |
-
{ "word": "UNDERFLOW", "clue": "Insufficient data error" },
|
202 |
-
{ "word": "EXCEPTION", "clue": "Error handling mechanism" },
|
203 |
-
{ "word": "INTERRUPT", "clue": "Process suspension signal" },
|
204 |
-
{ "word": "SIGNAL", "clue": "Process communication" },
|
205 |
-
{ "word": "EVENT", "clue": "System occurrence" },
|
206 |
-
{ "word": "HANDLER", "clue": "Event processing function" },
|
207 |
-
{ "word": "CALLBACK", "clue": "Function reference" },
|
208 |
-
{ "word": "PROMISE", "clue": "Future value placeholder" },
|
209 |
-
{ "word": "ASYNC", "clue": "Non-blocking operation" },
|
210 |
-
{ "word": "AWAIT", "clue": "Pause for completion" },
|
211 |
-
{ "word": "YIELD", "clue": "Temporary function pause" },
|
212 |
-
{ "word": "GENERATOR", "clue": "Value sequence producer" },
|
213 |
-
{ "word": "ITERATOR", "clue": "Sequential access pattern" },
|
214 |
-
{ "word": "RECURSION", "clue": "Self-calling function" },
|
215 |
-
{ "word": "CLOSURE", "clue": "Function scope retention" },
|
216 |
-
{ "word": "LAMBDA", "clue": "Anonymous function" },
|
217 |
-
{ "word": "FUNCTIONAL", "clue": "Function-based programming" },
|
218 |
-
{ "word": "PROCEDURAL", "clue": "Step-by-step programming" },
|
219 |
-
{ "word": "DECLARATIVE", "clue": "What-not-how programming" },
|
220 |
-
{ "word": "IMPERATIVE", "clue": "Command-based programming" }
|
221 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/package-lock.json
DELETED
The diff for this file is too large to render.
See raw diff
|
|
crossword-app/backend/package.json
DELETED
@@ -1,53 +0,0 @@
|
|
1 |
-
{
|
2 |
-
"name": "crossword-backend",
|
3 |
-
"version": "1.0.0",
|
4 |
-
"description": "Node.js backend for crossword puzzle generator",
|
5 |
-
"main": "src/app.js",
|
6 |
-
"type": "commonjs",
|
7 |
-
"scripts": {
|
8 |
-
"start": "node src/app.js",
|
9 |
-
"dev": "nodemon src/app.js",
|
10 |
-
"build:frontend": "cd ../frontend && npm run build",
|
11 |
-
"build": "npm run build:frontend && mkdir -p public && cp -r ../frontend/dist/* public/",
|
12 |
-
"test": "jest",
|
13 |
-
"test:watch": "jest --watch",
|
14 |
-
"lint": "eslint src/",
|
15 |
-
"lint:fix": "eslint src/ --fix",
|
16 |
-
"format": "prettier --write \"src/**/*.js\"",
|
17 |
-
"db:migrate": "node scripts/migrate.js",
|
18 |
-
"db:seed": "node scripts/seed.js",
|
19 |
-
"db:reset": "npm run db:migrate && npm run db:seed"
|
20 |
-
},
|
21 |
-
"dependencies": {
|
22 |
-
"@huggingface/inference": "^4.7.1",
|
23 |
-
"compression": "^1.7.4",
|
24 |
-
"cors": "^2.8.5",
|
25 |
-
"dotenv": "^16.3.1",
|
26 |
-
"express": "^4.18.2",
|
27 |
-
"express-rate-limit": "^7.1.5",
|
28 |
-
"helmet": "^7.1.0",
|
29 |
-
"pg": "^8.11.3"
|
30 |
-
},
|
31 |
-
"devDependencies": {
|
32 |
-
"axios": "^1.11.0",
|
33 |
-
"eslint": "^8.55.0",
|
34 |
-
"jest": "^29.7.0",
|
35 |
-
"nodemon": "^3.0.2",
|
36 |
-
"prettier": "^3.1.1",
|
37 |
-
"supertest": "^6.3.3"
|
38 |
-
},
|
39 |
-
"engines": {
|
40 |
-
"node": ">=18.0.0",
|
41 |
-
"npm": ">=9.0.0"
|
42 |
-
},
|
43 |
-
"keywords": [
|
44 |
-
"crossword",
|
45 |
-
"puzzle",
|
46 |
-
"word-game",
|
47 |
-
"api",
|
48 |
-
"nodejs",
|
49 |
-
"express"
|
50 |
-
],
|
51 |
-
"author": "Crossword App Team",
|
52 |
-
"license": "MIT"
|
53 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/public/assets/index-Bkj8ir_U.js
DELETED
@@ -1,10 +0,0 @@
|
|
1 |
-
import{r as m,a as R,R as k}from"./vendor-nf7bT_Uh.js";(function(){const c=document.createElement("link").relList;if(c&&c.supports&&c.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))n(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&n(a)}).observe(document,{childList:!0,subtree:!0});function i(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(s){if(s.ep)return;s.ep=!0;const r=i(s);fetch(s.href,r)}})();var S={exports:{}},b={};/**
|
2 |
-
* @license React
|
3 |
-
* react-jsx-runtime.production.min.js
|
4 |
-
*
|
5 |
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
6 |
-
*
|
7 |
-
* This source code is licensed under the MIT license found in the
|
8 |
-
* LICENSE file in the root directory of this source tree.
|
9 |
-
*/var E=m,_=Symbol.for("react.element"),$=Symbol.for("react.fragment"),T=Object.prototype.hasOwnProperty,O=E.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,A={key:!0,ref:!0,__self:!0,__source:!0};function C(t,c,i){var n,s={},r=null,a=null;i!==void 0&&(r=""+i),c.key!==void 0&&(r=""+c.key),c.ref!==void 0&&(a=c.ref);for(n in c)T.call(c,n)&&!A.hasOwnProperty(n)&&(s[n]=c[n]);if(t&&t.defaultProps)for(n in c=t.defaultProps,c)s[n]===void 0&&(s[n]=c[n]);return{$$typeof:_,type:t,key:r,ref:a,props:s,_owner:O.current}}b.Fragment=$;b.jsx=C;b.jsxs=C;S.exports=b;var e=S.exports,v={},w=R;v.createRoot=w.createRoot,v.hydrateRoot=w.hydrateRoot;const L=({onTopicsChange:t,availableTopics:c=[],selectedTopics:i=[],useAI:n=!1,onAIToggle:s})=>{const r=a=>{const g=i.includes(a)?i.filter(o=>o!==a):[...i,a];t(g)};return e.jsxs("div",{className:"topic-selector",children:[e.jsx("h3",{children:"Select Topics"}),e.jsx("div",{className:"topic-buttons",children:c.map(a=>e.jsx("button",{className:`topic-btn ${i.includes(a.name)?"selected":""}`,onClick:()=>r(a.name),children:a.name},a.id))}),e.jsxs("div",{className:"ai-toggle-container",children:[e.jsxs("label",{className:"ai-toggle",children:[e.jsx("input",{type:"checkbox",checked:n,onChange:a=>s(a.target.checked),className:"ai-checkbox"}),e.jsxs("span",{className:"ai-label",children:["🤖 Use AI-powered word generation",n&&e.jsx("span",{className:"ai-status",children:" (Dynamic content)"})]})]}),e.jsx("p",{className:"ai-description",children:n?"AI will generate unique words based on semantic relationships":"Using curated word lists with quality clues"})]}),e.jsxs("p",{className:"selected-count",children:[i.length," topic",i.length!==1?"s":""," selected"]})]})},D=({grid:t,clues:c,showSolution:i,onCellChange:n})=>{const[s,r]=m.useState({}),a=(u,l,d)=>{const p=`${u}-${l}`,h={...s,[p]:d.toUpperCase()};r(h),n&&n(u,l,d)},g=(u,l)=>{if(i&&!o(u,l))return t[u][l];const d=`${u}-${l}`;return s[d]||""},o=(u,l)=>t[u][l]===".",f=(u,l)=>{if(!c)return null;const d=c.find(p=>p.position.row===u&&p.position.col===l);return d?d.number:null};if(!t||t.length===0)return e.jsx("div",{className:"puzzle-grid",children:"No puzzle loaded"});const x=t.length,j=t[0]?t[0].length:0;return e.jsx("div",{className:"puzzle-container",children:e.jsx("div",{className:"puzzle-grid",style:{gridTemplateColumns:`repeat(${j}, 35px)`,gridTemplateRows:`repeat(${x}, 35px)`},children:t.map((u,l)=>u.map((d,p)=>{const h=f(l,p);return o(l,p)?e.jsx("div",{className:"grid-cell empty-cell",style:{visibility:"hidden"}},`${l}-${p}`):e.jsxs("div",{className:"grid-cell white-cell",children:[h&&e.jsx("span",{className:"cell-number",children:h}),e.jsx("input",{type:"text",maxLength:"1",value:g(l,p),onChange:z=>a(l,p,z.target.value),className:`cell-input ${i?"solution-text":""}`,disabled:i})]},`${l}-${p}`)}))})})},G=({clues:t=[]})=>{const c=t.filter(s=>s.direction==="across"),i=t.filter(s=>s.direction==="down"),n=({title:s,clueList:r})=>e.jsxs("div",{className:"clue-section",children:[e.jsx("h4",{children:s}),e.jsx("ol",{children:r.map(a=>e.jsxs("li",{className:"clue-item",children:[e.jsx("span",{className:"clue-number",children:a.number}),e.jsx("span",{className:"clue-text",children:a.text})]},`${a.number}-${a.direction}`))})]});return e.jsxs("div",{className:"clue-list",children:[e.jsx(n,{title:"Across",clueList:c}),e.jsx(n,{title:"Down",clueList:i})]})},U=({message:t="Generating puzzle..."})=>e.jsxs("div",{className:"loading-spinner",children:[e.jsx("div",{className:"spinner"}),e.jsx("p",{className:"loading-message",children:t})]}),F=()=>{const[t,c]=m.useState(null),[i,n]=m.useState(!1),[s,r]=m.useState(null),[a,g]=m.useState([]),o="",f=m.useCallback(async()=>{try{n(!0);const l=await fetch(`${o}/api/topics`);if(!l.ok)throw new Error("Failed to fetch topics");const d=await l.json();g(d)}catch(l){r(l.message)}finally{n(!1)}},[o]),x=m.useCallback(async(l,d="medium",p=!1)=>{try{n(!0),r(null);const h=await fetch(`${o}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({topics:l,difficulty:d,useAI:p})});if(!h.ok){const z=await h.json().catch(()=>({}));throw new Error(z.message||"Failed to generate puzzle")}const y=await h.json();return c(y),y}catch(h){return r(h.message),null}finally{n(!1)}},[o]),j=m.useCallback(async l=>{try{const d=await fetch(`${o}/api/validate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({puzzle:t,answers:l})});if(!d.ok)throw new Error("Failed to validate answers");return await d.json()}catch(d){return r(d.message),null}},[o,t]),u=m.useCallback(()=>{c(null),r(null)},[]);return{puzzle:t,loading:i,error:s,topics:a,fetchTopics:f,generatePuzzle:x,validateAnswers:j,resetPuzzle:u}};function B(){const[t,c]=m.useState([]),[i,n]=m.useState("medium"),[s,r]=m.useState(!1),[a,g]=m.useState(!1),{puzzle:o,loading:f,error:x,topics:j,fetchTopics:u,generatePuzzle:l,resetPuzzle:d}=F();m.useEffect(()=>{u()},[u]);const p=async()=>{if(t.length===0){alert("Please select at least one topic");return}await l(t,i,a)},h=N=>{c(N)},y=N=>{g(N)},z=()=>{d(),c([]),r(!1),g(!1),n("medium")},P=()=>{r(!0)};return e.jsxs("div",{className:"crossword-app",children:[e.jsxs("header",{className:"app-header",children:[e.jsx("h1",{className:"app-title",children:"Crossword Puzzle Generator"}),e.jsx("p",{children:"Select topics and generate your custom crossword puzzle!"})]}),e.jsx(L,{onTopicsChange:h,availableTopics:j,selectedTopics:t,useAI:a,onAIToggle:y}),e.jsxs("div",{className:"puzzle-controls",children:[e.jsxs("select",{value:i,onChange:N=>n(N.target.value),className:"control-btn",disabled:!0,title:"Difficulty selection temporarily disabled - using Medium difficulty",children:[e.jsx("option",{value:"easy",children:"Easy"}),e.jsx("option",{value:"medium",children:"Medium"}),e.jsx("option",{value:"hard",children:"Hard"})]}),e.jsx("button",{onClick:p,disabled:f||t.length===0,className:"control-btn generate-btn",children:f?"Generating...":"Generate Puzzle"}),e.jsx("button",{onClick:z,className:"control-btn reset-btn",children:"Reset"}),o&&!s&&e.jsx("button",{onClick:P,className:"control-btn reveal-btn",children:"Reveal Solution"})]}),x&&e.jsxs("div",{className:"error-message",children:["Error: ",x]}),f&&e.jsx(U,{}),o&&!f&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"puzzle-info",children:[e.jsxs("span",{className:"puzzle-stats",children:[o.metadata.wordCount," words • ",o.metadata.size,"×",o.metadata.size," grid"]}),o.metadata.aiGenerated&&e.jsx("span",{className:"ai-generated-badge",children:"🤖 AI-Enhanced"})]}),e.jsxs("div",{className:"puzzle-layout",children:[e.jsx(D,{grid:o.grid,clues:o.clues,showSolution:s}),e.jsx(G,{clues:o.clues})]})]}),!o&&!f&&!x&&e.jsx("div",{style:{textAlign:"center",padding:"40px",color:"#7f8c8d"},children:'Select topics and click "Generate Puzzle" to start!'})]})}v.createRoot(document.getElementById("root")).render(e.jsx(k.StrictMode,{children:e.jsx(B,{})}));
|
10 |
-
//# sourceMappingURL=index-Bkj8ir_U.js.map
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/public/assets/index-Bkj8ir_U.js.map
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
{"version":3,"file":"index-Bkj8ir_U.js","sources":["../../node_modules/react/cjs/react-jsx-runtime.production.min.js","../../node_modules/react/jsx-runtime.js","../../node_modules/react-dom/client.js","../../src/components/TopicSelector.jsx","../../src/components/PuzzleGrid.jsx","../../src/components/ClueList.jsx","../../src/components/LoadingSpinner.jsx","../../src/hooks/useCrossword.js","../../src/App.jsx","../../src/main.jsx"],"sourcesContent":["/**\n * @license React\n * react-jsx-runtime.production.min.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n'use strict';var f=require(\"react\"),k=Symbol.for(\"react.element\"),l=Symbol.for(\"react.fragment\"),m=Object.prototype.hasOwnProperty,n=f.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,p={key:!0,ref:!0,__self:!0,__source:!0};\nfunction q(c,a,g){var b,d={},e=null,h=null;void 0!==g&&(e=\"\"+g);void 0!==a.key&&(e=\"\"+a.key);void 0!==a.ref&&(h=a.ref);for(b in a)m.call(a,b)&&!p.hasOwnProperty(b)&&(d[b]=a[b]);if(c&&c.defaultProps)for(b in a=c.defaultProps,a)void 0===d[b]&&(d[b]=a[b]);return{$$typeof:k,type:c,key:e,ref:h,props:d,_owner:n.current}}exports.Fragment=l;exports.jsx=q;exports.jsxs=q;\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.min.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","'use strict';\n\nvar m = require('react-dom');\nif (process.env.NODE_ENV === 'production') {\n exports.createRoot = m.createRoot;\n exports.hydrateRoot = m.hydrateRoot;\n} else {\n var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n exports.createRoot = function(c, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.createRoot(c, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n exports.hydrateRoot = function(c, h, o) {\n i.usingClientEntryPoint = true;\n try {\n return m.hydrateRoot(c, h, o);\n } finally {\n i.usingClientEntryPoint = false;\n }\n };\n}\n","import React from 'react';\n\nconst TopicSelector = ({ \n onTopicsChange, \n availableTopics = [], \n selectedTopics = [],\n useAI = false,\n onAIToggle\n}) => {\n const handleTopicToggle = (topic) => {\n const newSelectedTopics = selectedTopics.includes(topic)\n ? selectedTopics.filter(t => t !== topic)\n : [...selectedTopics, topic];\n \n onTopicsChange(newSelectedTopics);\n };\n\n return (\n <div className=\"topic-selector\">\n <h3>Select Topics</h3>\n <div className=\"topic-buttons\">\n {availableTopics.map(topic => (\n <button\n key={topic.id}\n className={`topic-btn ${selectedTopics.includes(topic.name) ? 'selected' : ''}`}\n onClick={() => handleTopicToggle(topic.name)}\n >\n {topic.name}\n </button>\n ))}\n </div>\n \n <div className=\"ai-toggle-container\">\n <label className=\"ai-toggle\">\n <input\n type=\"checkbox\"\n checked={useAI}\n onChange={(e) => onAIToggle(e.target.checked)}\n className=\"ai-checkbox\"\n />\n <span className=\"ai-label\">\n 🤖 Use AI-powered word generation\n {useAI && <span className=\"ai-status\"> (Dynamic content)</span>}\n </span>\n </label>\n <p className=\"ai-description\">\n {useAI \n ? \"AI will generate unique words based on semantic relationships\" \n : \"Using curated word lists with quality clues\"\n }\n </p>\n </div>\n \n <p className=\"selected-count\">\n {selectedTopics.length} topic{selectedTopics.length !== 1 ? 's' : ''} selected\n </p>\n </div>\n );\n};\n\nexport default TopicSelector;","import React, { useState } from 'react';\n\nconst PuzzleGrid = ({ grid, clues, showSolution, onCellChange }) => {\n const [userAnswers, setUserAnswers] = useState({});\n\n const handleCellInput = (row, col, value) => {\n const key = `${row}-${col}`;\n const newAnswers = { ...userAnswers, [key]: value.toUpperCase() };\n setUserAnswers(newAnswers);\n onCellChange && onCellChange(row, col, value);\n };\n\n const getCellValue = (row, col) => {\n if (showSolution && !isBlackCell(row, col)) {\n return grid[row][col];\n }\n const key = `${row}-${col}`;\n return userAnswers[key] || '';\n };\n\n const isBlackCell = (row, col) => {\n return grid[row][col] === '.';\n };\n\n const getCellNumber = (row, col) => {\n if (!clues) return null;\n const clue = clues.find(c => c.position.row === row && c.position.col === col);\n return clue ? clue.number : null;\n };\n\n if (!grid || grid.length === 0) {\n return <div className=\"puzzle-grid\">No puzzle loaded</div>;\n }\n\n const gridRows = grid.length;\n const gridCols = grid[0] ? grid[0].length : 0;\n\n return (\n <div className=\"puzzle-container\">\n <div \n className=\"puzzle-grid\"\n style={{\n gridTemplateColumns: `repeat(${gridCols}, 35px)`,\n gridTemplateRows: `repeat(${gridRows}, 35px)`\n }}\n >\n {grid.map((row, rowIndex) =>\n row.map((cell, colIndex) => {\n const cellNumber = getCellNumber(rowIndex, colIndex);\n const isBlack = isBlackCell(rowIndex, colIndex);\n \n // Only render cells that contain letters (not black/unused cells)\n if (isBlack) {\n return (\n <div\n key={`${rowIndex}-${colIndex}`}\n className=\"grid-cell empty-cell\"\n style={{ visibility: 'hidden' }}\n >\n </div>\n );\n }\n \n return (\n <div\n key={`${rowIndex}-${colIndex}`}\n className=\"grid-cell white-cell\"\n >\n {cellNumber && <span className=\"cell-number\">{cellNumber}</span>}\n <input\n type=\"text\"\n maxLength=\"1\"\n value={getCellValue(rowIndex, colIndex)}\n onChange={(e) => handleCellInput(rowIndex, colIndex, e.target.value)}\n className={`cell-input ${showSolution ? 'solution-text' : ''}`}\n disabled={showSolution}\n />\n </div>\n );\n })\n )}\n </div>\n </div>\n );\n};\n\nexport default PuzzleGrid;","import React from 'react';\n\nconst ClueList = ({ clues = [] }) => {\n const acrossClues = clues.filter(clue => clue.direction === 'across');\n const downClues = clues.filter(clue => clue.direction === 'down');\n\n const ClueSection = ({ title, clueList }) => (\n <div className=\"clue-section\">\n <h4>{title}</h4>\n <ol>\n {clueList.map(clue => (\n <li key={`${clue.number}-${clue.direction}`} className=\"clue-item\">\n <span className=\"clue-number\">{clue.number}</span>\n <span className=\"clue-text\">{clue.text}</span>\n </li>\n ))}\n </ol>\n </div>\n );\n\n return (\n <div className=\"clue-list\">\n <ClueSection title=\"Across\" clueList={acrossClues} />\n <ClueSection title=\"Down\" clueList={downClues} />\n </div>\n );\n};\n\nexport default ClueList;","import React from 'react';\n\nconst LoadingSpinner = ({ message = \"Generating puzzle...\" }) => {\n return (\n <div className=\"loading-spinner\">\n <div className=\"spinner\"></div>\n <p className=\"loading-message\">{message}</p>\n </div>\n );\n};\n\nexport default LoadingSpinner;","import { useState, useCallback } from 'react';\n\nconst useCrossword = () => {\n const [puzzle, setPuzzle] = useState(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n const [topics, setTopics] = useState([]);\n\n const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || (import.meta.env.PROD ? '' : 'http://localhost:3000');\n\n const fetchTopics = useCallback(async () => {\n try {\n setLoading(true);\n const response = await fetch(`${API_BASE_URL}/api/topics`);\n if (!response.ok) throw new Error('Failed to fetch topics');\n const data = await response.json();\n setTopics(data);\n } catch (err) {\n setError(err.message);\n } finally {\n setLoading(false);\n }\n }, [API_BASE_URL]);\n\n const generatePuzzle = useCallback(async (selectedTopics, difficulty = 'medium', useAI = false) => {\n try {\n setLoading(true);\n setError(null);\n \n const response = await fetch(`${API_BASE_URL}/api/generate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n topics: selectedTopics,\n difficulty,\n useAI\n })\n });\n\n if (!response.ok) {\n const errorData = await response.json().catch(() => ({}));\n throw new Error(errorData.message || 'Failed to generate puzzle');\n }\n \n const puzzleData = await response.json();\n setPuzzle(puzzleData);\n return puzzleData;\n } catch (err) {\n setError(err.message);\n return null;\n } finally {\n setLoading(false);\n }\n }, [API_BASE_URL]);\n\n const validateAnswers = useCallback(async (userAnswers) => {\n try {\n const response = await fetch(`${API_BASE_URL}/api/validate`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n puzzle: puzzle,\n answers: userAnswers\n })\n });\n\n if (!response.ok) throw new Error('Failed to validate answers');\n \n return await response.json();\n } catch (err) {\n setError(err.message);\n return null;\n }\n }, [API_BASE_URL, puzzle]);\n\n const resetPuzzle = useCallback(() => {\n setPuzzle(null);\n setError(null);\n }, []);\n\n return {\n puzzle,\n loading,\n error,\n topics,\n fetchTopics,\n generatePuzzle,\n validateAnswers,\n resetPuzzle\n };\n};\n\nexport default useCrossword;","import React, { useState, useEffect } from 'react';\nimport TopicSelector from './components/TopicSelector';\nimport PuzzleGrid from './components/PuzzleGrid';\nimport ClueList from './components/ClueList';\nimport LoadingSpinner from './components/LoadingSpinner';\nimport useCrossword from './hooks/useCrossword';\nimport './styles/puzzle.css';\n\nfunction App() {\n const [selectedTopics, setSelectedTopics] = useState([]);\n const [difficulty, setDifficulty] = useState('medium');\n const [showSolution, setShowSolution] = useState(false);\n const [useAI, setUseAI] = useState(false);\n \n const {\n puzzle,\n loading,\n error,\n topics,\n fetchTopics,\n generatePuzzle,\n resetPuzzle\n } = useCrossword();\n\n useEffect(() => {\n fetchTopics();\n }, [fetchTopics]);\n\n const handleGeneratePuzzle = async () => {\n if (selectedTopics.length === 0) {\n alert('Please select at least one topic');\n return;\n }\n \n await generatePuzzle(selectedTopics, difficulty, useAI);\n };\n\n const handleTopicsChange = (topics) => {\n setSelectedTopics(topics);\n };\n\n const handleAIToggle = (aiEnabled) => {\n setUseAI(aiEnabled);\n };\n\n const handleReset = () => {\n resetPuzzle();\n setSelectedTopics([]);\n setShowSolution(false);\n setUseAI(false);\n setDifficulty('medium'); // Always reset to medium\n };\n\n const handleRevealSolution = () => {\n setShowSolution(true);\n };\n\n return (\n <div className=\"crossword-app\">\n <header className=\"app-header\">\n <h1 className=\"app-title\">Crossword Puzzle Generator</h1>\n <p>Select topics and generate your custom crossword puzzle!</p>\n </header>\n\n <TopicSelector \n onTopicsChange={handleTopicsChange}\n availableTopics={topics}\n selectedTopics={selectedTopics}\n useAI={useAI}\n onAIToggle={handleAIToggle}\n />\n\n <div className=\"puzzle-controls\">\n <select \n value={difficulty} \n onChange={(e) => setDifficulty(e.target.value)}\n className=\"control-btn\"\n disabled\n title=\"Difficulty selection temporarily disabled - using Medium difficulty\"\n >\n <option value=\"easy\">Easy</option>\n <option value=\"medium\">Medium</option>\n <option value=\"hard\">Hard</option>\n </select>\n \n <button\n onClick={handleGeneratePuzzle}\n disabled={loading || selectedTopics.length === 0}\n className=\"control-btn generate-btn\"\n >\n {loading ? 'Generating...' : 'Generate Puzzle'}\n </button>\n \n <button\n onClick={handleReset}\n className=\"control-btn reset-btn\"\n >\n Reset\n </button>\n \n {puzzle && !showSolution && (\n <button\n onClick={handleRevealSolution}\n className=\"control-btn reveal-btn\"\n >\n Reveal Solution\n </button>\n )}\n </div>\n\n {error && (\n <div className=\"error-message\">\n Error: {error}\n </div>\n )}\n\n {loading && <LoadingSpinner />}\n\n {puzzle && !loading && (\n <>\n <div className=\"puzzle-info\">\n <span className=\"puzzle-stats\">\n {puzzle.metadata.wordCount} words • {puzzle.metadata.size}×{puzzle.metadata.size} grid\n </span>\n {puzzle.metadata.aiGenerated && (\n <span className=\"ai-generated-badge\">🤖 AI-Enhanced</span>\n )}\n </div>\n <div className=\"puzzle-layout\">\n <PuzzleGrid \n grid={puzzle.grid} \n clues={puzzle.clues}\n showSolution={showSolution}\n />\n <ClueList clues={puzzle.clues} />\n </div>\n </>\n )}\n\n {!puzzle && !loading && !error && (\n <div style={{ textAlign: 'center', padding: '40px', color: '#7f8c8d' }}>\n Select topics and click \"Generate Puzzle\" to start!\n </div>\n )}\n </div>\n );\n}\n\nexport default App;","import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App.jsx'\n\nReactDOM.createRoot(document.getElementById('root')).render(\n <React.StrictMode>\n <App />\n </React.StrictMode>,\n)"],"names":["f","require$$0","k","l","m","n","p","q","c","a","g","b","d","e","h","reactJsxRuntime_production_min","jsxRuntimeModule","client","TopicSelector","onTopicsChange","availableTopics","selectedTopics","useAI","onAIToggle","handleTopicToggle","topic","newSelectedTopics","t","jsxs","jsx","PuzzleGrid","grid","clues","showSolution","onCellChange","userAnswers","setUserAnswers","useState","handleCellInput","row","col","value","key","newAnswers","getCellValue","isBlackCell","getCellNumber","clue","gridRows","gridCols","rowIndex","cell","colIndex","cellNumber","ClueList","acrossClues","downClues","ClueSection","title","clueList","LoadingSpinner","message","useCrossword","puzzle","setPuzzle","loading","setLoading","error","setError","topics","setTopics","API_BASE_URL","fetchTopics","useCallback","response","data","err","generatePuzzle","difficulty","errorData","puzzleData","validateAnswers","resetPuzzle","App","setSelectedTopics","setDifficulty","setShowSolution","setUseAI","useEffect","handleGeneratePuzzle","handleTopicsChange","handleAIToggle","aiEnabled","handleReset","handleRevealSolution","Fragment","ReactDOM","React"],"mappings":"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","x_google_ignoreList":[0,1,2]}
|
|
|
|
crossword-app/backend/public/assets/index-V4v18wFW.css
DELETED
@@ -1 +0,0 @@
|
|
1 |
-
.crossword-app{max-width:1200px;margin:0 auto;padding:20px;font-family:Segoe UI,Tahoma,Geneva,Verdana,sans-serif}.app-header{text-align:center;margin-bottom:30px}.app-title{color:#2c3e50;font-size:2.5rem;margin-bottom:10px}.topic-selector{background:#f8f9fa;padding:20px;border-radius:8px;margin-bottom:20px}.topic-selector h3{margin-top:0;color:#2c3e50}.topic-buttons{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:15px}.topic-btn{padding:8px 16px;border:2px solid #3498db;background:#fff;color:#3498db;border-radius:20px;cursor:pointer;transition:all .3s ease;font-weight:500}.topic-btn:hover,.topic-btn.selected{background:#3498db;color:#fff}.selected-count{color:#7f8c8d;font-size:.9rem;margin:0}.ai-toggle-container{margin:20px 0;padding:15px;background:#f8f9fa;border-radius:8px;border:2px solid #e9ecef;transition:all .3s ease}.ai-toggle-container:has(.ai-checkbox:checked){background:linear-gradient(135deg,#e3f2fd,#f3e5f5);border-color:#3498db}.ai-toggle{display:flex;align-items:center;cursor:pointer;font-weight:500;margin-bottom:8px}.ai-checkbox{width:20px;height:20px;margin-right:12px;cursor:pointer;accent-color:#3498db}.ai-label{font-size:1rem;color:#2c3e50;-webkit-user-select:none;user-select:none}.ai-status{color:#27ae60;font-weight:600;font-size:.9rem}.ai-description{margin:0;font-size:.85rem;color:#6c757d;line-height:1.4;padding-left:32px}.puzzle-controls{display:flex;gap:15px;margin-bottom:20px;justify-content:center}.control-btn{padding:10px 20px;border:none;border-radius:5px;cursor:pointer;font-weight:600;transition:background-color .3s ease}.control-btn:disabled{background:#bdc3c7!important;color:#7f8c8d!important;cursor:not-allowed;opacity:.7}.generate-btn{background:#27ae60;color:#fff}.generate-btn:hover{background:#229954}.generate-btn:disabled{background:#bdc3c7;cursor:not-allowed}.reset-btn{background:#e74c3c;color:#fff}.reset-btn:hover{background:#c0392b}.reveal-btn{background:#f39c12;color:#fff}.reveal-btn:hover{background:#e67e22}.loading-spinner{display:flex;flex-direction:column;align-items:center;padding:40px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #3498db;border-radius:50%;animation:spin 1s linear infinite;margin-bottom:15px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-message{color:#7f8c8d;font-size:1.1rem}.puzzle-info{display:flex;justify-content:space-between;align-items:center;margin:20px 0 10px;padding:10px 15px;background:#f8f9fa;border-radius:6px;border-left:4px solid #3498db}.puzzle-stats{font-size:.9rem;color:#6c757d;font-weight:500}.ai-generated-badge{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:4px 12px;border-radius:15px;font-size:.8rem;font-weight:600;text-shadow:0 1px 2px rgba(0,0,0,.2);box-shadow:0 2px 4px #0000001a}.puzzle-layout{display:grid;grid-template-columns:1fr 300px;gap:30px;margin-top:20px}@media (max-width: 768px){.puzzle-layout{grid-template-columns:1fr;gap:20px}.puzzle-info{flex-direction:column;gap:8px;text-align:center}.ai-toggle-container{padding:12px}.ai-description{padding-left:0;text-align:center}}.puzzle-container{display:flex;justify-content:center}.puzzle-grid{display:grid;gap:0;margin:0 auto;width:fit-content;height:fit-content}.grid-cell{width:35px;height:35px;position:relative;display:flex;align-items:center;justify-content:center;box-sizing:border-box;background:#fff}.grid-cell:before{content:"";position:absolute;top:0;left:0;right:-1px;bottom:-1px;border:1px solid #2c3e50;pointer-events:none;z-index:10}.black-cell{background:#f0f0f0}.black-cell:before{background:#f0f0f0;border:1px solid #2c3e50}.white-cell{background:#fff}.empty-cell{background:transparent;border:none;visibility:hidden}.empty-cell:before{display:none}.cell-input{width:100%;height:100%;border:none!important;text-align:center;font-size:16px;font-weight:700;background:transparent;outline:none;text-transform:uppercase;position:relative;z-index:5}.cell-input:focus{background:#e8f4fd;box-shadow:inset 0 0 0 2px #3498db}.cell-number{position:absolute;top:1px;left:2px;font-size:10px;font-weight:700;color:#2c3e50;line-height:1;z-index:15;pointer-events:none}.solution-text{color:#2c3e50!important;font-weight:700!important;background:#fff!important}.solution-text:disabled{opacity:1!important;cursor:default}.grid-cell .solution-text{border:none!important;background:#fff!important}.clue-list{background:#f8f9fa;padding:20px;border-radius:8px;max-height:600px;overflow-y:auto}.clue-section{margin-bottom:25px}.clue-section h4{color:#2c3e50;margin-bottom:15px;font-size:1.2rem;border-bottom:2px solid #3498db;padding-bottom:5px}.clue-section ol{padding-left:0;list-style:none}.clue-item{display:flex;margin-bottom:8px;padding:8px;border-radius:4px;cursor:pointer;transition:background-color .2s ease}.clue-item:hover{background:#e9ecef}.clue-number{font-weight:700;color:#3498db;margin-right:10px;min-width:25px}.clue-text{flex:1;color:#2c3e50}.error-message{background:#f8d7da;color:#721c24;padding:15px;border-radius:5px;margin:20px 0;border:1px solid #f5c6cb}.success-message{background:#d4edda;color:#155724;padding:15px;border-radius:5px;margin:20px 0;border:1px solid #c3e6cb;text-align:center;font-weight:600}
|
|
|
|
crossword-app/backend/public/assets/vendor-nf7bT_Uh.js
DELETED
The diff for this file is too large to render.
See raw diff
|
|
crossword-app/backend/public/assets/vendor-nf7bT_Uh.js.map
DELETED
The diff for this file is too large to render.
See raw diff
|
|
crossword-app/backend/public/index.html
DELETED
@@ -1,16 +0,0 @@
|
|
1 |
-
<!DOCTYPE html>
|
2 |
-
<html lang="en">
|
3 |
-
<head>
|
4 |
-
<meta charset="UTF-8" />
|
5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
-
<meta name="description" content="Generate custom crossword puzzles by selecting topics" />
|
7 |
-
<meta name="keywords" content="crossword, puzzle, word game, brain teaser" />
|
8 |
-
<title>Crossword Puzzle Generator</title>
|
9 |
-
<script type="module" crossorigin src="/assets/index-Bkj8ir_U.js"></script>
|
10 |
-
<link rel="modulepreload" crossorigin href="/assets/vendor-nf7bT_Uh.js">
|
11 |
-
<link rel="stylesheet" crossorigin href="/assets/index-V4v18wFW.css">
|
12 |
-
</head>
|
13 |
-
<body>
|
14 |
-
<div id="root"></div>
|
15 |
-
</body>
|
16 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/setup-env.sh
DELETED
@@ -1,33 +0,0 @@
|
|
1 |
-
#!/bin/bash
|
2 |
-
|
3 |
-
# Crossword App - Environment Setup Script
|
4 |
-
echo "🛠️ Setting up Crossword App environment..."
|
5 |
-
|
6 |
-
# Check if .env already exists
|
7 |
-
if [ -f ".env" ]; then
|
8 |
-
echo "⚠️ .env file already exists!"
|
9 |
-
echo " Backup created as .env.backup"
|
10 |
-
cp .env .env.backup
|
11 |
-
fi
|
12 |
-
|
13 |
-
# Copy template
|
14 |
-
echo "📋 Copying .env.example to .env..."
|
15 |
-
cp .env.example .env
|
16 |
-
|
17 |
-
echo "✅ Environment file created!"
|
18 |
-
echo ""
|
19 |
-
echo "🔑 Next steps:"
|
20 |
-
echo " 1. Edit .env file with your settings:"
|
21 |
-
echo " nano .env"
|
22 |
-
echo ""
|
23 |
-
echo " 2. Add your HuggingFace API key:"
|
24 |
-
echo " HUGGINGFACE_API_KEY=hf_your_real_key_here"
|
25 |
-
echo ""
|
26 |
-
echo " 3. Optionally enable AI by default:"
|
27 |
-
echo " USE_AI_WORDS=true"
|
28 |
-
echo ""
|
29 |
-
echo " 4. Start the development server:"
|
30 |
-
echo " npm run dev"
|
31 |
-
echo ""
|
32 |
-
echo "📚 Get HuggingFace API key: https://huggingface.co/settings/tokens"
|
33 |
-
echo "🔒 Your .env file is gitignored and secure!"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/app.js
DELETED
@@ -1,175 +0,0 @@
|
|
1 |
-
require('dotenv').config();
|
2 |
-
|
3 |
-
// Suppress HuggingFace verbose logging
|
4 |
-
process.env.HF_HUB_VERBOSITY = 'error';
|
5 |
-
process.env.TRANSFORMERS_VERBOSITY = 'error';
|
6 |
-
|
7 |
-
const express = require('express');
|
8 |
-
const cors = require('cors');
|
9 |
-
const helmet = require('helmet');
|
10 |
-
const rateLimit = require('express-rate-limit');
|
11 |
-
const path = require('path');
|
12 |
-
const apiRoutes = require('./routes/api');
|
13 |
-
|
14 |
-
const app = express();
|
15 |
-
const PORT = process.env.PORT || 7860;
|
16 |
-
|
17 |
-
// Trust proxy for platforms like Hugging Face Spaces, Heroku, etc.
|
18 |
-
if (process.env.NODE_ENV === 'production') {
|
19 |
-
app.set('trust proxy', 1);
|
20 |
-
}
|
21 |
-
|
22 |
-
app.use(helmet());
|
23 |
-
|
24 |
-
// CORS configuration
|
25 |
-
let corsOptions;
|
26 |
-
if (process.env.NODE_ENV === 'production') {
|
27 |
-
// In production, allow same origin (since frontend is served from same server)
|
28 |
-
corsOptions = {
|
29 |
-
origin: true,
|
30 |
-
credentials: true,
|
31 |
-
optionsSuccessStatus: 200
|
32 |
-
};
|
33 |
-
} else {
|
34 |
-
// Development mode - allow dev servers
|
35 |
-
corsOptions = {
|
36 |
-
origin: process.env.CORS_ORIGIN || ['http://localhost:5173', 'http://localhost:3000'],
|
37 |
-
credentials: true,
|
38 |
-
optionsSuccessStatus: 200
|
39 |
-
};
|
40 |
-
}
|
41 |
-
app.use(cors(corsOptions));
|
42 |
-
|
43 |
-
const limiter = rateLimit({
|
44 |
-
windowMs: 15 * 60 * 1000,
|
45 |
-
max: 100,
|
46 |
-
message: 'Too many requests from this IP, please try again later.',
|
47 |
-
standardHeaders: true,
|
48 |
-
legacyHeaders: false,
|
49 |
-
});
|
50 |
-
app.use(limiter);
|
51 |
-
|
52 |
-
const generateLimiter = rateLimit({
|
53 |
-
windowMs: 5 * 60 * 1000,
|
54 |
-
max: 50,
|
55 |
-
message: 'Too many puzzle generation requests, please wait before trying again.',
|
56 |
-
});
|
57 |
-
|
58 |
-
app.use(express.json({ limit: '10mb' }));
|
59 |
-
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
60 |
-
|
61 |
-
// Basic request logging without detailed headers
|
62 |
-
app.use((req, res, next) => {
|
63 |
-
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
|
64 |
-
next();
|
65 |
-
});
|
66 |
-
|
67 |
-
app.use('/api/generate', generateLimiter);
|
68 |
-
app.use('/api', apiRoutes);
|
69 |
-
|
70 |
-
// Serve static files in production
|
71 |
-
if (process.env.NODE_ENV === 'production') {
|
72 |
-
const staticPath = path.join(__dirname, '../public');
|
73 |
-
console.log(`Static files path: ${staticPath}`);
|
74 |
-
|
75 |
-
// Check if static files exist
|
76 |
-
const fs = require('fs');
|
77 |
-
try {
|
78 |
-
const files = fs.readdirSync(staticPath);
|
79 |
-
console.log(`Static files found:`, files);
|
80 |
-
|
81 |
-
// Check assets directory specifically
|
82 |
-
const assetsPath = path.join(staticPath, 'assets');
|
83 |
-
if (fs.existsSync(assetsPath)) {
|
84 |
-
const assetFiles = fs.readdirSync(assetsPath);
|
85 |
-
console.log(`Assets directory contents:`, assetFiles);
|
86 |
-
} else {
|
87 |
-
console.error(`Assets directory not found at: ${assetsPath}`);
|
88 |
-
}
|
89 |
-
} catch (error) {
|
90 |
-
console.error(`Error reading static files directory:`, error.message);
|
91 |
-
}
|
92 |
-
|
93 |
-
app.use(express.static(staticPath));
|
94 |
-
|
95 |
-
// Test route to check if static serving works
|
96 |
-
app.get('/test-static', (req, res) => {
|
97 |
-
const fs = require('fs');
|
98 |
-
const assetsPath = path.join(staticPath, 'assets');
|
99 |
-
try {
|
100 |
-
const files = fs.readdirSync(assetsPath);
|
101 |
-
res.json({ staticPath, assetsPath, files });
|
102 |
-
} catch (error) {
|
103 |
-
res.status(500).json({ error: error.message, staticPath, assetsPath });
|
104 |
-
}
|
105 |
-
});
|
106 |
-
|
107 |
-
|
108 |
-
// Handle React Router routes - serve index.html for non-API routes
|
109 |
-
app.get('*', (req, res) => {
|
110 |
-
// Ensure we're sending the right content type
|
111 |
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
112 |
-
res.sendFile(path.join(staticPath, 'index.html'));
|
113 |
-
});
|
114 |
-
} else {
|
115 |
-
// Development mode - show API info
|
116 |
-
app.get('/', (req, res) => {
|
117 |
-
res.json({
|
118 |
-
message: 'Crossword Puzzle API',
|
119 |
-
version: '1.0.0',
|
120 |
-
mode: 'development',
|
121 |
-
endpoints: {
|
122 |
-
topics: 'GET /api/topics',
|
123 |
-
generate: 'POST /api/generate',
|
124 |
-
validate: 'POST /api/validate',
|
125 |
-
words: 'GET /api/words/:topic',
|
126 |
-
health: 'GET /api/health'
|
127 |
-
}
|
128 |
-
});
|
129 |
-
});
|
130 |
-
}
|
131 |
-
|
132 |
-
app.use((req, res) => {
|
133 |
-
res.status(404).json({
|
134 |
-
error: 'Not Found',
|
135 |
-
message: `Route ${req.method} ${req.path} not found`,
|
136 |
-
timestamp: new Date().toISOString()
|
137 |
-
});
|
138 |
-
});
|
139 |
-
|
140 |
-
app.use((error, req, res, next) => {
|
141 |
-
console.error('Error:', error);
|
142 |
-
|
143 |
-
if (error.type === 'entity.parse.failed') {
|
144 |
-
return res.status(400).json({
|
145 |
-
error: 'Invalid JSON',
|
146 |
-
message: 'Request body contains invalid JSON'
|
147 |
-
});
|
148 |
-
}
|
149 |
-
|
150 |
-
res.status(500).json({
|
151 |
-
error: 'Internal Server Error',
|
152 |
-
message: process.env.NODE_ENV === 'production'
|
153 |
-
? 'Something went wrong'
|
154 |
-
: error.message,
|
155 |
-
timestamp: new Date().toISOString()
|
156 |
-
});
|
157 |
-
});
|
158 |
-
|
159 |
-
if (require.main === module) {
|
160 |
-
const HOST = process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost';
|
161 |
-
app.listen(PORT, HOST, () => {
|
162 |
-
console.log(`Server running on ${HOST}:${PORT}`);
|
163 |
-
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
164 |
-
console.log(`Working directory: ${process.cwd()}`);
|
165 |
-
console.log(`App file location: ${__dirname}`);
|
166 |
-
if (process.env.NODE_ENV === 'production') {
|
167 |
-
console.log(`Serving React app from /public directory`);
|
168 |
-
console.log(`CORS enabled for same origin`);
|
169 |
-
} else {
|
170 |
-
console.log(`CORS enabled for: ${JSON.stringify(corsOptions.origin)}`);
|
171 |
-
}
|
172 |
-
});
|
173 |
-
}
|
174 |
-
|
175 |
-
module.exports = app;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/controllers/puzzleController.js
DELETED
@@ -1,65 +0,0 @@
|
|
1 |
-
const CrosswordGenerator = require('../services/crosswordGenerator');
|
2 |
-
const WordService = require('../services/wordService');
|
3 |
-
|
4 |
-
class PuzzleController {
|
5 |
-
static async getTopics(req, res) {
|
6 |
-
try {
|
7 |
-
const topics = await WordService.getAllTopics();
|
8 |
-
res.json(topics);
|
9 |
-
} catch (error) {
|
10 |
-
console.error('Error fetching topics:', error);
|
11 |
-
res.status(500).json({ error: 'Failed to fetch topics' });
|
12 |
-
}
|
13 |
-
}
|
14 |
-
|
15 |
-
static async generatePuzzle(req, res) {
|
16 |
-
try {
|
17 |
-
const { topics, difficulty = 'medium', useAI = false } = req.body;
|
18 |
-
|
19 |
-
if (!topics || !Array.isArray(topics) || topics.length === 0) {
|
20 |
-
return res.status(400).json({ error: 'Topics array is required' });
|
21 |
-
}
|
22 |
-
|
23 |
-
const generator = new CrosswordGenerator();
|
24 |
-
const puzzle = await generator.generatePuzzle(topics, difficulty, { useAI });
|
25 |
-
|
26 |
-
if (!puzzle) {
|
27 |
-
return res.status(400).json({ error: 'Could not generate puzzle with selected topics' });
|
28 |
-
}
|
29 |
-
|
30 |
-
res.json(puzzle);
|
31 |
-
} catch (error) {
|
32 |
-
console.error('Error generating puzzle:', error);
|
33 |
-
res.status(500).json({ error: 'Failed to generate puzzle' });
|
34 |
-
}
|
35 |
-
}
|
36 |
-
|
37 |
-
static async validateAnswers(req, res) {
|
38 |
-
try {
|
39 |
-
const { puzzle, answers } = req.body;
|
40 |
-
|
41 |
-
if (!puzzle || !answers) {
|
42 |
-
return res.status(400).json({ error: 'Puzzle and answers are required' });
|
43 |
-
}
|
44 |
-
|
45 |
-
const validation = CrosswordGenerator.validateAnswers(puzzle, answers);
|
46 |
-
res.json(validation);
|
47 |
-
} catch (error) {
|
48 |
-
console.error('Error validating answers:', error);
|
49 |
-
res.status(500).json({ error: 'Failed to validate answers' });
|
50 |
-
}
|
51 |
-
}
|
52 |
-
|
53 |
-
static async getWords(req, res) {
|
54 |
-
try {
|
55 |
-
const { topic } = req.params;
|
56 |
-
const words = await WordService.getWordsByTopic(topic);
|
57 |
-
res.json(words);
|
58 |
-
} catch (error) {
|
59 |
-
console.error('Error fetching words:', error);
|
60 |
-
res.status(500).json({ error: 'Failed to fetch words' });
|
61 |
-
}
|
62 |
-
}
|
63 |
-
}
|
64 |
-
|
65 |
-
module.exports = PuzzleController;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/models/Topic.js
DELETED
@@ -1,144 +0,0 @@
|
|
1 |
-
class Topic {
|
2 |
-
constructor({ id, name, description, wordCount = 0, difficulty = 'medium' }) {
|
3 |
-
this.id = id;
|
4 |
-
this.name = name;
|
5 |
-
this.description = description;
|
6 |
-
this.wordCount = wordCount;
|
7 |
-
this.difficulty = difficulty;
|
8 |
-
this.createdAt = new Date();
|
9 |
-
this.updatedAt = new Date();
|
10 |
-
}
|
11 |
-
|
12 |
-
static validateTopic(topicData) {
|
13 |
-
const errors = [];
|
14 |
-
|
15 |
-
if (!topicData.name || typeof topicData.name !== 'string') {
|
16 |
-
errors.push('Topic name is required and must be a string');
|
17 |
-
} else if (topicData.name.length < 2 || topicData.name.length > 50) {
|
18 |
-
errors.push('Topic name must be between 2 and 50 characters');
|
19 |
-
}
|
20 |
-
|
21 |
-
if (topicData.description && typeof topicData.description !== 'string') {
|
22 |
-
errors.push('Description must be a string');
|
23 |
-
}
|
24 |
-
|
25 |
-
if (topicData.difficulty && !['easy', 'medium', 'hard'].includes(topicData.difficulty)) {
|
26 |
-
errors.push('Difficulty must be easy, medium, or hard');
|
27 |
-
}
|
28 |
-
|
29 |
-
return {
|
30 |
-
isValid: errors.length === 0,
|
31 |
-
errors
|
32 |
-
};
|
33 |
-
}
|
34 |
-
|
35 |
-
static fromJSON(data) {
|
36 |
-
return new Topic(data);
|
37 |
-
}
|
38 |
-
|
39 |
-
toJSON() {
|
40 |
-
return {
|
41 |
-
id: this.id,
|
42 |
-
name: this.name,
|
43 |
-
description: this.description,
|
44 |
-
wordCount: this.wordCount,
|
45 |
-
difficulty: this.difficulty,
|
46 |
-
createdAt: this.createdAt,
|
47 |
-
updatedAt: this.updatedAt
|
48 |
-
};
|
49 |
-
}
|
50 |
-
|
51 |
-
updateWordCount(count) {
|
52 |
-
this.wordCount = count;
|
53 |
-
this.updatedAt = new Date();
|
54 |
-
}
|
55 |
-
|
56 |
-
getDisplayName() {
|
57 |
-
return this.name
|
58 |
-
.split(/[-_\s]+/)
|
59 |
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
60 |
-
.join(' ');
|
61 |
-
}
|
62 |
-
|
63 |
-
getSlug() {
|
64 |
-
return this.name
|
65 |
-
.toLowerCase()
|
66 |
-
.replace(/[^a-z0-9]+/g, '-')
|
67 |
-
.replace(/^-+|-+$/g, '');
|
68 |
-
}
|
69 |
-
|
70 |
-
isPopular() {
|
71 |
-
return this.wordCount >= 20;
|
72 |
-
}
|
73 |
-
|
74 |
-
getDifficultyLevel() {
|
75 |
-
const levels = {
|
76 |
-
easy: 1,
|
77 |
-
medium: 2,
|
78 |
-
hard: 3
|
79 |
-
};
|
80 |
-
|
81 |
-
return levels[this.difficulty] || 2;
|
82 |
-
}
|
83 |
-
|
84 |
-
static getDefaultTopics() {
|
85 |
-
return [
|
86 |
-
new Topic({
|
87 |
-
id: 'animals',
|
88 |
-
name: 'Animals',
|
89 |
-
description: 'Various animals from around the world',
|
90 |
-
wordCount: 25,
|
91 |
-
difficulty: 'easy'
|
92 |
-
}),
|
93 |
-
new Topic({
|
94 |
-
id: 'science',
|
95 |
-
name: 'Science',
|
96 |
-
description: 'Scientific terms and concepts',
|
97 |
-
wordCount: 30,
|
98 |
-
difficulty: 'medium'
|
99 |
-
}),
|
100 |
-
new Topic({
|
101 |
-
id: 'geography',
|
102 |
-
name: 'Geography',
|
103 |
-
description: 'Countries, cities, and geographical features',
|
104 |
-
wordCount: 28,
|
105 |
-
difficulty: 'medium'
|
106 |
-
}),
|
107 |
-
new Topic({
|
108 |
-
id: 'technology',
|
109 |
-
name: 'Technology',
|
110 |
-
description: 'Computer and technology terms',
|
111 |
-
wordCount: 35,
|
112 |
-
difficulty: 'hard'
|
113 |
-
}),
|
114 |
-
new Topic({
|
115 |
-
id: 'history',
|
116 |
-
name: 'History',
|
117 |
-
description: 'Historical events and figures',
|
118 |
-
wordCount: 22,
|
119 |
-
difficulty: 'hard'
|
120 |
-
}),
|
121 |
-
new Topic({
|
122 |
-
id: 'sports',
|
123 |
-
name: 'Sports',
|
124 |
-
description: 'Sports and athletic activities',
|
125 |
-
wordCount: 20,
|
126 |
-
difficulty: 'easy'
|
127 |
-
})
|
128 |
-
];
|
129 |
-
}
|
130 |
-
|
131 |
-
static sortByDifficulty(topics) {
|
132 |
-
return topics.sort((a, b) => a.getDifficultyLevel() - b.getDifficultyLevel());
|
133 |
-
}
|
134 |
-
|
135 |
-
static sortByPopularity(topics) {
|
136 |
-
return topics.sort((a, b) => b.wordCount - a.wordCount);
|
137 |
-
}
|
138 |
-
|
139 |
-
static filterByDifficulty(topics, difficulty) {
|
140 |
-
return topics.filter(topic => topic.difficulty === difficulty);
|
141 |
-
}
|
142 |
-
}
|
143 |
-
|
144 |
-
module.exports = Topic;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/models/Word.js
DELETED
@@ -1,112 +0,0 @@
|
|
1 |
-
class Word {
|
2 |
-
constructor({ id, word, clue, topic, difficulty = 'medium', length }) {
|
3 |
-
this.id = id;
|
4 |
-
this.word = word.toUpperCase();
|
5 |
-
this.clue = clue;
|
6 |
-
this.topic = topic;
|
7 |
-
this.difficulty = difficulty;
|
8 |
-
this.length = length || word.length;
|
9 |
-
this.createdAt = new Date();
|
10 |
-
}
|
11 |
-
|
12 |
-
static validateWord(wordData) {
|
13 |
-
const errors = [];
|
14 |
-
|
15 |
-
if (!wordData.word || typeof wordData.word !== 'string') {
|
16 |
-
errors.push('Word is required and must be a string');
|
17 |
-
} else if (wordData.word.length < 2 || wordData.word.length > 15) {
|
18 |
-
errors.push('Word must be between 2 and 15 characters');
|
19 |
-
} else if (!/^[A-Za-z]+$/.test(wordData.word)) {
|
20 |
-
errors.push('Word must contain only letters');
|
21 |
-
}
|
22 |
-
|
23 |
-
if (!wordData.clue || typeof wordData.clue !== 'string') {
|
24 |
-
errors.push('Clue is required and must be a string');
|
25 |
-
}
|
26 |
-
|
27 |
-
if (!wordData.topic || typeof wordData.topic !== 'string') {
|
28 |
-
errors.push('Topic is required and must be a string');
|
29 |
-
}
|
30 |
-
|
31 |
-
if (wordData.difficulty && !['easy', 'medium', 'hard'].includes(wordData.difficulty)) {
|
32 |
-
errors.push('Difficulty must be easy, medium, or hard');
|
33 |
-
}
|
34 |
-
|
35 |
-
return {
|
36 |
-
isValid: errors.length === 0,
|
37 |
-
errors
|
38 |
-
};
|
39 |
-
}
|
40 |
-
|
41 |
-
static fromJSON(data) {
|
42 |
-
return new Word(data);
|
43 |
-
}
|
44 |
-
|
45 |
-
toJSON() {
|
46 |
-
return {
|
47 |
-
id: this.id,
|
48 |
-
word: this.word,
|
49 |
-
clue: this.clue,
|
50 |
-
topic: this.topic,
|
51 |
-
difficulty: this.difficulty,
|
52 |
-
length: this.length,
|
53 |
-
createdAt: this.createdAt
|
54 |
-
};
|
55 |
-
}
|
56 |
-
|
57 |
-
getDifficultyScore() {
|
58 |
-
const baseScore = this.length;
|
59 |
-
const difficultyMultiplier = {
|
60 |
-
easy: 1,
|
61 |
-
medium: 1.2,
|
62 |
-
hard: 1.5
|
63 |
-
};
|
64 |
-
|
65 |
-
return Math.round(baseScore * (difficultyMultiplier[this.difficulty] || 1));
|
66 |
-
}
|
67 |
-
|
68 |
-
isCompatibleWith(otherWord) {
|
69 |
-
if (!otherWord || !(otherWord instanceof Word)) {
|
70 |
-
return false;
|
71 |
-
}
|
72 |
-
|
73 |
-
const thisWord = this.word;
|
74 |
-
const otherWordStr = otherWord.word;
|
75 |
-
|
76 |
-
for (let i = 0; i < thisWord.length; i++) {
|
77 |
-
for (let j = 0; j < otherWordStr.length; j++) {
|
78 |
-
if (thisWord[i] === otherWordStr[j]) {
|
79 |
-
return true;
|
80 |
-
}
|
81 |
-
}
|
82 |
-
}
|
83 |
-
|
84 |
-
return false;
|
85 |
-
}
|
86 |
-
|
87 |
-
getIntersectionPoints(otherWord) {
|
88 |
-
if (!this.isCompatibleWith(otherWord)) {
|
89 |
-
return [];
|
90 |
-
}
|
91 |
-
|
92 |
-
const intersections = [];
|
93 |
-
const thisWord = this.word;
|
94 |
-
const otherWordStr = otherWord.word;
|
95 |
-
|
96 |
-
for (let i = 0; i < thisWord.length; i++) {
|
97 |
-
for (let j = 0; j < otherWordStr.length; j++) {
|
98 |
-
if (thisWord[i] === otherWordStr[j]) {
|
99 |
-
intersections.push({
|
100 |
-
thisIndex: i,
|
101 |
-
otherIndex: j,
|
102 |
-
letter: thisWord[i]
|
103 |
-
});
|
104 |
-
}
|
105 |
-
}
|
106 |
-
}
|
107 |
-
|
108 |
-
return intersections;
|
109 |
-
}
|
110 |
-
}
|
111 |
-
|
112 |
-
module.exports = Word;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/routes/api.js
DELETED
@@ -1,82 +0,0 @@
|
|
1 |
-
const express = require('express');
|
2 |
-
const PuzzleController = require('../controllers/puzzleController');
|
3 |
-
const EmbeddingWordService = require('../services/embeddingWordService');
|
4 |
-
|
5 |
-
const router = express.Router();
|
6 |
-
|
7 |
-
router.get('/topics', PuzzleController.getTopics);
|
8 |
-
|
9 |
-
router.post('/generate', PuzzleController.generatePuzzle);
|
10 |
-
|
11 |
-
router.post('/validate', PuzzleController.validateAnswers);
|
12 |
-
|
13 |
-
router.get('/words/:topic', PuzzleController.getWords);
|
14 |
-
|
15 |
-
router.get('/health', (req, res) => {
|
16 |
-
res.json({
|
17 |
-
status: 'OK',
|
18 |
-
timestamp: new Date().toISOString(),
|
19 |
-
service: 'Crossword API'
|
20 |
-
});
|
21 |
-
});
|
22 |
-
|
23 |
-
// AI/Embedding service endpoints
|
24 |
-
router.get('/ai/status', async (req, res) => {
|
25 |
-
try {
|
26 |
-
const stats = EmbeddingWordService.getCacheStats();
|
27 |
-
res.json({
|
28 |
-
status: 'OK',
|
29 |
-
aiService: {
|
30 |
-
initialized: stats.isInitialized,
|
31 |
-
fallbackEnabled: stats.fallbackEnabled,
|
32 |
-
cacheStats: {
|
33 |
-
embeddingCache: stats.embeddingCacheSize,
|
34 |
-
wordCache: stats.wordCacheSize
|
35 |
-
}
|
36 |
-
},
|
37 |
-
timestamp: new Date().toISOString()
|
38 |
-
});
|
39 |
-
} catch (error) {
|
40 |
-
res.status(500).json({
|
41 |
-
status: 'ERROR',
|
42 |
-
error: error.message,
|
43 |
-
timestamp: new Date().toISOString()
|
44 |
-
});
|
45 |
-
}
|
46 |
-
});
|
47 |
-
|
48 |
-
router.post('/ai/generate-words', async (req, res) => {
|
49 |
-
try {
|
50 |
-
const { topics, difficulty = 'medium', count = 12 } = req.body;
|
51 |
-
|
52 |
-
if (!topics || !Array.isArray(topics) || topics.length === 0) {
|
53 |
-
return res.status(400).json({
|
54 |
-
error: 'Topics array is required',
|
55 |
-
example: { topics: ['animals', 'science'], difficulty: 'medium', count: 12 }
|
56 |
-
});
|
57 |
-
}
|
58 |
-
|
59 |
-
const words = await EmbeddingWordService.generateWordsForTopics(topics, difficulty, count);
|
60 |
-
|
61 |
-
res.json({
|
62 |
-
success: true,
|
63 |
-
topics,
|
64 |
-
difficulty,
|
65 |
-
requestedCount: count,
|
66 |
-
generatedCount: words.length,
|
67 |
-
words: words,
|
68 |
-
timestamp: new Date().toISOString(),
|
69 |
-
aiGenerated: EmbeddingWordService.getCacheStats().isInitialized
|
70 |
-
});
|
71 |
-
|
72 |
-
} catch (error) {
|
73 |
-
console.error('Error generating AI words:', error);
|
74 |
-
res.status(500).json({
|
75 |
-
error: 'Failed to generate words',
|
76 |
-
message: error.message,
|
77 |
-
timestamp: new Date().toISOString()
|
78 |
-
});
|
79 |
-
}
|
80 |
-
});
|
81 |
-
|
82 |
-
module.exports = router;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/services/crosswordGenerator.js
DELETED
@@ -1,982 +0,0 @@
|
|
1 |
-
const WordService = require('./wordService');
|
2 |
-
const EmbeddingWordService = require('./embeddingWordService');
|
3 |
-
|
4 |
-
class CrosswordGenerator {
|
5 |
-
constructor() {
|
6 |
-
this.maxAttempts = 100;
|
7 |
-
this.minWords = 6;
|
8 |
-
this.maxWords = 12;
|
9 |
-
this.useAI = process.env.USE_AI_WORDS === 'true';
|
10 |
-
}
|
11 |
-
|
12 |
-
async generatePuzzle(topics, difficulty = 'medium', options = {}) {
|
13 |
-
try {
|
14 |
-
const useAI = options.useAI !== undefined ? options.useAI : this.useAI;
|
15 |
-
|
16 |
-
const words = await this.selectWords(topics, difficulty, useAI);
|
17 |
-
|
18 |
-
// Log word->clue mapping for debugging
|
19 |
-
this.logWordClueMapping(words, useAI, topics);
|
20 |
-
|
21 |
-
if (words.length < this.minWords) {
|
22 |
-
console.error(`❌ Not enough words: ${words.length} < ${this.minWords}`);
|
23 |
-
throw new Error('Not enough words available for selected topics');
|
24 |
-
}
|
25 |
-
|
26 |
-
const gridResult = this.createGrid(words);
|
27 |
-
|
28 |
-
if (!gridResult) {
|
29 |
-
console.error(`❌ Grid creation failed - could not place words`);
|
30 |
-
throw new Error('Could not place words in grid');
|
31 |
-
}
|
32 |
-
|
33 |
-
const clues = this.generateClues(words, gridResult.placedWords);
|
34 |
-
|
35 |
-
return {
|
36 |
-
grid: gridResult.grid,
|
37 |
-
clues: clues,
|
38 |
-
metadata: {
|
39 |
-
topics,
|
40 |
-
difficulty,
|
41 |
-
wordCount: words.length,
|
42 |
-
size: gridResult.size,
|
43 |
-
aiGenerated: useAI && EmbeddingWordService.getCacheStats().isInitialized
|
44 |
-
}
|
45 |
-
};
|
46 |
-
} catch (error) {
|
47 |
-
console.error('❌ Error generating puzzle:', error);
|
48 |
-
return null;
|
49 |
-
}
|
50 |
-
}
|
51 |
-
|
52 |
-
async selectWords(topics, difficulty, useAI = false) {
|
53 |
-
try {
|
54 |
-
if (useAI) {
|
55 |
-
console.log(`🤖 Using AI-powered word generation for topics: ${topics.join(', ')}`);
|
56 |
-
const aiWords = await EmbeddingWordService.generateWordsForTopics(topics, difficulty, this.maxWords);
|
57 |
-
|
58 |
-
if (aiWords.length >= this.minWords) {
|
59 |
-
console.log(`✅ AI generated ${aiWords.length} words successfully`);
|
60 |
-
return aiWords;
|
61 |
-
} else {
|
62 |
-
console.log(`⚠️ AI only generated ${aiWords.length} words, falling back to static`);
|
63 |
-
}
|
64 |
-
}
|
65 |
-
|
66 |
-
// Fallback to static word selection
|
67 |
-
console.log(`📚 Using static word selection for topics: ${topics.join(', ')}`);
|
68 |
-
const allWords = [];
|
69 |
-
|
70 |
-
for (const topic of topics) {
|
71 |
-
const topicWords = await WordService.getWordsByTopic(topic);
|
72 |
-
allWords.push(...topicWords);
|
73 |
-
}
|
74 |
-
|
75 |
-
const filteredWords = this.filterWordsByDifficulty(allWords, difficulty);
|
76 |
-
|
77 |
-
// Sort words to prioritize those with good intersection potential
|
78 |
-
const sortedWords = this.sortWordsForCrossword(filteredWords);
|
79 |
-
|
80 |
-
return sortedWords.slice(0, this.maxWords);
|
81 |
-
|
82 |
-
} catch (error) {
|
83 |
-
console.error('❌ Error in word selection:', error.message);
|
84 |
-
// Final fallback to basic static selection
|
85 |
-
return await this.getBasicStaticWords(topics, difficulty);
|
86 |
-
}
|
87 |
-
}
|
88 |
-
|
89 |
-
async getBasicStaticWords(topics, difficulty) {
|
90 |
-
try {
|
91 |
-
const allWords = [];
|
92 |
-
for (const topic of topics) {
|
93 |
-
const topicWords = await WordService.getWordsByTopic(topic);
|
94 |
-
allWords.push(...topicWords.slice(0, 20)); // Limit per topic
|
95 |
-
}
|
96 |
-
const filtered = this.filterWordsByDifficulty(allWords, difficulty);
|
97 |
-
return filtered.slice(0, this.maxWords);
|
98 |
-
} catch (error) {
|
99 |
-
console.error('❌ Even basic word selection failed:', error);
|
100 |
-
return [];
|
101 |
-
}
|
102 |
-
}
|
103 |
-
|
104 |
-
sortWordsForCrossword(words) {
|
105 |
-
// Score words based on their crossword potential
|
106 |
-
const scoredWords = words.map(wordObj => {
|
107 |
-
const word = wordObj.word;
|
108 |
-
let score = 0;
|
109 |
-
|
110 |
-
// Moderate preference for medium-length words (5-7 chars optimal)
|
111 |
-
if (word.length >= 5 && word.length <= 7) {
|
112 |
-
score += 10; // Good base score for medium words
|
113 |
-
} else if (word.length >= 3 && word.length <= 4) {
|
114 |
-
score += 5; // Short words get some points
|
115 |
-
} else if (word.length >= 8) {
|
116 |
-
score += 3; // Long words get fewer points
|
117 |
-
}
|
118 |
-
|
119 |
-
// Words with common letters get bonus points
|
120 |
-
const commonLetters = ['E', 'A', 'R', 'I', 'O', 'T', 'N', 'S'];
|
121 |
-
for (const letter of word) {
|
122 |
-
if (commonLetters.includes(letter)) {
|
123 |
-
score += 1;
|
124 |
-
}
|
125 |
-
}
|
126 |
-
|
127 |
-
// Vowel distribution bonus
|
128 |
-
const vowels = ['A', 'E', 'I', 'O', 'U'];
|
129 |
-
const vowelCount = word.split('').filter(letter => vowels.includes(letter)).length;
|
130 |
-
score += vowelCount;
|
131 |
-
|
132 |
-
return { ...wordObj, crosswordScore: score };
|
133 |
-
});
|
134 |
-
|
135 |
-
// Sort by score (descending) then shuffle within score groups
|
136 |
-
scoredWords.sort((a, b) => {
|
137 |
-
if (Math.abs(a.crosswordScore - b.crosswordScore) <= 3) {
|
138 |
-
return Math.random() - 0.5; // Shuffle similar scores
|
139 |
-
}
|
140 |
-
return b.crosswordScore - a.crosswordScore;
|
141 |
-
});
|
142 |
-
|
143 |
-
return scoredWords;
|
144 |
-
}
|
145 |
-
|
146 |
-
filterWordsByDifficulty(words, difficulty) {
|
147 |
-
const difficultyMap = {
|
148 |
-
easy: { minLen: 3, maxLen: 8 },
|
149 |
-
medium: { minLen: 4, maxLen: 10 },
|
150 |
-
hard: { minLen: 5, maxLen: 15 }
|
151 |
-
};
|
152 |
-
|
153 |
-
const { minLen, maxLen } = difficultyMap[difficulty] || difficultyMap.medium;
|
154 |
-
|
155 |
-
return words.filter(word =>
|
156 |
-
word.word.length >= minLen && word.word.length <= maxLen
|
157 |
-
);
|
158 |
-
}
|
159 |
-
|
160 |
-
createGrid(words) {
|
161 |
-
if (!words || words.length === 0) {
|
162 |
-
console.error(`❌ No words provided to createGrid`);
|
163 |
-
return null;
|
164 |
-
}
|
165 |
-
|
166 |
-
const wordList = words.map(w => w.word.toUpperCase()).sort((a, b) => b.length - a.length);
|
167 |
-
const size = this.calculateGridSize(wordList);
|
168 |
-
|
169 |
-
// Try with different grid sizes and word counts
|
170 |
-
for (let attempt = 0; attempt < 3; attempt++) {
|
171 |
-
const currentSize = size + attempt;
|
172 |
-
|
173 |
-
// First try with all words
|
174 |
-
let result = this.placeWordsInGrid(wordList, currentSize);
|
175 |
-
if (result) {
|
176 |
-
return { grid: result.grid, size: currentSize, placedWords: result.placedWords };
|
177 |
-
}
|
178 |
-
|
179 |
-
// If that fails, try with 1-2 fewer words (but not too aggressive)
|
180 |
-
if (wordList.length > 7) {
|
181 |
-
const reducedWords = wordList.slice(0, wordList.length - 1);
|
182 |
-
result = this.placeWordsInGrid(reducedWords, currentSize);
|
183 |
-
if (result) {
|
184 |
-
return { grid: result.grid, size: currentSize, placedWords: result.placedWords };
|
185 |
-
}
|
186 |
-
}
|
187 |
-
}
|
188 |
-
|
189 |
-
// Only as last resort, try with minimum words
|
190 |
-
for (let attempt = 0; attempt < 2; attempt++) {
|
191 |
-
const currentSize = size + attempt;
|
192 |
-
if (wordList.length >= 6) {
|
193 |
-
const minWords = wordList.slice(0, 6);
|
194 |
-
const result = this.placeWordsInGrid(minWords, currentSize);
|
195 |
-
if (result) {
|
196 |
-
return { grid: result.grid, size: currentSize, placedWords: result.placedWords };
|
197 |
-
}
|
198 |
-
}
|
199 |
-
}
|
200 |
-
|
201 |
-
// Absolute last resort: simple cross pattern with just 2 words
|
202 |
-
if (wordList.length >= 2) {
|
203 |
-
const result = this.createSimpleCross(wordList.slice(0, 2));
|
204 |
-
if (result) {
|
205 |
-
return result;
|
206 |
-
}
|
207 |
-
}
|
208 |
-
|
209 |
-
console.error(`❌ All grid creation attempts failed`);
|
210 |
-
return null;
|
211 |
-
}
|
212 |
-
|
213 |
-
calculateGridSize(words) {
|
214 |
-
const totalChars = words.reduce((sum, word) => sum + word.length, 0);
|
215 |
-
const longestWord = Math.max(...words.map(word => word.length));
|
216 |
-
return Math.max(
|
217 |
-
Math.ceil(Math.sqrt(totalChars * 1.5)),
|
218 |
-
longestWord,
|
219 |
-
8
|
220 |
-
);
|
221 |
-
}
|
222 |
-
|
223 |
-
trimGrid(grid, placedWords) {
|
224 |
-
if (!placedWords || placedWords.length === 0) return grid;
|
225 |
-
|
226 |
-
// Find the bounds of the actual puzzle
|
227 |
-
let minRow = grid.length, maxRow = -1;
|
228 |
-
let minCol = grid[0].length, maxCol = -1;
|
229 |
-
|
230 |
-
placedWords.forEach(word => {
|
231 |
-
const { row, col, direction } = word;
|
232 |
-
const wordLength = word.word.length;
|
233 |
-
|
234 |
-
minRow = Math.min(minRow, row);
|
235 |
-
minCol = Math.min(minCol, col);
|
236 |
-
|
237 |
-
if (direction === 'horizontal') {
|
238 |
-
maxRow = Math.max(maxRow, row);
|
239 |
-
maxCol = Math.max(maxCol, col + wordLength - 1);
|
240 |
-
} else {
|
241 |
-
maxRow = Math.max(maxRow, row + wordLength - 1);
|
242 |
-
maxCol = Math.max(maxCol, col);
|
243 |
-
}
|
244 |
-
});
|
245 |
-
|
246 |
-
// Add small padding
|
247 |
-
minRow = Math.max(0, minRow - 1);
|
248 |
-
minCol = Math.max(0, minCol - 1);
|
249 |
-
maxRow = Math.min(grid.length - 1, maxRow + 1);
|
250 |
-
maxCol = Math.min(grid[0].length - 1, maxCol + 1);
|
251 |
-
|
252 |
-
// Create trimmed grid
|
253 |
-
const trimmedGrid = [];
|
254 |
-
for (let r = minRow; r <= maxRow; r++) {
|
255 |
-
const row = [];
|
256 |
-
for (let c = minCol; c <= maxCol; c++) {
|
257 |
-
row.push(grid[r][c]);
|
258 |
-
}
|
259 |
-
trimmedGrid.push(row);
|
260 |
-
}
|
261 |
-
|
262 |
-
// Update placed words positions to match trimmed grid
|
263 |
-
const updatedPlacedWords = placedWords.map(word => ({
|
264 |
-
...word,
|
265 |
-
row: word.row - minRow,
|
266 |
-
col: word.col - minCol
|
267 |
-
}));
|
268 |
-
|
269 |
-
return { grid: trimmedGrid, placedWords: updatedPlacedWords };
|
270 |
-
}
|
271 |
-
|
272 |
-
placeWordsInGrid(words, size) {
|
273 |
-
const grid = Array(size).fill().map(() => Array(size).fill('.'));
|
274 |
-
const placedWords = [];
|
275 |
-
|
276 |
-
const startTime = Date.now();
|
277 |
-
const timeout = 5000; // Reduced to 5 second timeout
|
278 |
-
|
279 |
-
if (this.backtrackPlacement(grid, words, 0, placedWords, startTime, timeout)) {
|
280 |
-
const trimmed = this.trimGrid(grid, placedWords);
|
281 |
-
return { grid: trimmed.grid, placedWords: trimmed.placedWords };
|
282 |
-
}
|
283 |
-
|
284 |
-
return null;
|
285 |
-
}
|
286 |
-
|
287 |
-
backtrackPlacement(grid, words, wordIndex, placedWords, startTime = Date.now(), timeout = 5000, callCount = 0) {
|
288 |
-
// Check timeout more frequently
|
289 |
-
if (callCount % 50 === 0 && Date.now() - startTime > timeout) {
|
290 |
-
console.error(`⏰ Placement timeout reached after ${Date.now() - startTime}ms`);
|
291 |
-
return false;
|
292 |
-
}
|
293 |
-
|
294 |
-
if (wordIndex >= words.length) {
|
295 |
-
return true;
|
296 |
-
}
|
297 |
-
|
298 |
-
const word = words[wordIndex];
|
299 |
-
const size = grid.length;
|
300 |
-
|
301 |
-
// For the first word, place the longest word in the center horizontally
|
302 |
-
if (wordIndex === 0) {
|
303 |
-
const centerRow = Math.floor(size / 2);
|
304 |
-
const centerCol = Math.floor((size - word.length) / 2);
|
305 |
-
|
306 |
-
if (this.canPlaceWord(grid, word, centerRow, centerCol, 'horizontal')) {
|
307 |
-
const originalState = this.placeWord(grid, word, centerRow, centerCol, 'horizontal');
|
308 |
-
placedWords.push({ word, row: centerRow, col: centerCol, direction: 'horizontal', number: 1 });
|
309 |
-
|
310 |
-
if (this.backtrackPlacement(grid, words, wordIndex + 1, placedWords, startTime, timeout, callCount + 1)) {
|
311 |
-
return true;
|
312 |
-
}
|
313 |
-
|
314 |
-
this.removeWord(grid, originalState);
|
315 |
-
placedWords.pop();
|
316 |
-
}
|
317 |
-
return false;
|
318 |
-
}
|
319 |
-
|
320 |
-
// For the second word, try to create a central cross with smart selection
|
321 |
-
if (wordIndex === 1) {
|
322 |
-
const firstWord = placedWords[0];
|
323 |
-
|
324 |
-
// Try all remaining words to find one that can intersect
|
325 |
-
const remainingWords = words.slice(1); // All words except the first
|
326 |
-
const wordsWithIntersections = [];
|
327 |
-
|
328 |
-
// Score each remaining word by intersection potential
|
329 |
-
for (const candidateWord of remainingWords) {
|
330 |
-
const intersections = this.findWordIntersections(candidateWord, firstWord.word);
|
331 |
-
if (intersections.length > 0) {
|
332 |
-
wordsWithIntersections.push({
|
333 |
-
word: candidateWord,
|
334 |
-
intersectionCount: intersections.length,
|
335 |
-
intersections: intersections
|
336 |
-
});
|
337 |
-
}
|
338 |
-
}
|
339 |
-
|
340 |
-
// Sort by intersection count (more intersections = better)
|
341 |
-
wordsWithIntersections.sort((a, b) => b.intersectionCount - a.intersectionCount);
|
342 |
-
|
343 |
-
// Try each word with intersections
|
344 |
-
for (const candidateInfo of wordsWithIntersections) {
|
345 |
-
const candidateWord = candidateInfo.word;
|
346 |
-
|
347 |
-
for (const intersection of candidateInfo.intersections) {
|
348 |
-
const placement = this.calculateIntersectionPlacement(candidateWord, intersection.wordPos, firstWord, intersection.placedPos);
|
349 |
-
|
350 |
-
if (placement && this.canPlaceWord(grid, candidateWord, placement.row, placement.col, placement.direction)) {
|
351 |
-
const originalState = this.placeWord(grid, candidateWord, placement.row, placement.col, placement.direction);
|
352 |
-
placedWords.push({ word: candidateWord, ...placement, number: 2 });
|
353 |
-
|
354 |
-
// Create new word order with the selected second word
|
355 |
-
const newWordOrder = [words[0], candidateWord, ...words.slice(1).filter(w => w !== candidateWord)];
|
356 |
-
|
357 |
-
if (this.backtrackPlacement(grid, newWordOrder, wordIndex + 1, placedWords, startTime, timeout, callCount + 1)) {
|
358 |
-
return true;
|
359 |
-
}
|
360 |
-
|
361 |
-
this.removeWord(grid, originalState);
|
362 |
-
placedWords.pop();
|
363 |
-
}
|
364 |
-
}
|
365 |
-
}
|
366 |
-
|
367 |
-
return false;
|
368 |
-
}
|
369 |
-
|
370 |
-
// For subsequent words, find the best intersection opportunities
|
371 |
-
const allPlacements = this.findAllIntersectionPlacements(grid, word, placedWords);
|
372 |
-
|
373 |
-
// Sort by intersection quality score
|
374 |
-
allPlacements.sort((a, b) => b.score - a.score);
|
375 |
-
|
376 |
-
// Try the best placements first
|
377 |
-
for (const placement of allPlacements) {
|
378 |
-
const { row, col, direction } = placement;
|
379 |
-
|
380 |
-
if (this.canPlaceWord(grid, word, row, col, direction)) {
|
381 |
-
const originalState = this.placeWord(grid, word, row, col, direction);
|
382 |
-
placedWords.push({ word, row, col, direction, number: wordIndex + 1 });
|
383 |
-
|
384 |
-
if (this.backtrackPlacement(grid, words, wordIndex + 1, placedWords, startTime, timeout, callCount + 1)) {
|
385 |
-
return true;
|
386 |
-
}
|
387 |
-
|
388 |
-
this.removeWord(grid, originalState);
|
389 |
-
placedWords.pop();
|
390 |
-
}
|
391 |
-
}
|
392 |
-
|
393 |
-
return false;
|
394 |
-
}
|
395 |
-
|
396 |
-
canPlaceWord(grid, word, row, col, direction) {
|
397 |
-
const size = grid.length;
|
398 |
-
|
399 |
-
// Check boundaries first
|
400 |
-
if (row < 0 || col < 0 || row >= size || col >= size) {
|
401 |
-
return false;
|
402 |
-
}
|
403 |
-
|
404 |
-
if (direction === 'horizontal') {
|
405 |
-
if (col + word.length > size) return false;
|
406 |
-
|
407 |
-
for (let i = 0; i < word.length; i++) {
|
408 |
-
if (row >= size || col + i >= size || !grid[row] || grid[row][col + i] === undefined) {
|
409 |
-
return false;
|
410 |
-
}
|
411 |
-
const currentCell = grid[row][col + i];
|
412 |
-
if (currentCell !== '.' && currentCell !== word[i]) {
|
413 |
-
return false;
|
414 |
-
}
|
415 |
-
}
|
416 |
-
} else {
|
417 |
-
if (row + word.length > size) return false;
|
418 |
-
|
419 |
-
for (let i = 0; i < word.length; i++) {
|
420 |
-
if (row + i >= size || col >= size || !grid[row + i] || grid[row + i][col] === undefined) {
|
421 |
-
return false;
|
422 |
-
}
|
423 |
-
const currentCell = grid[row + i][col];
|
424 |
-
if (currentCell !== '.' && currentCell !== word[i]) {
|
425 |
-
return false;
|
426 |
-
}
|
427 |
-
}
|
428 |
-
}
|
429 |
-
|
430 |
-
return this.validateWordPlacement(grid, word, row, col, direction);
|
431 |
-
}
|
432 |
-
|
433 |
-
validateWordPlacement(grid, word, row, col, direction) {
|
434 |
-
const size = grid.length;
|
435 |
-
|
436 |
-
if (direction === 'horizontal') {
|
437 |
-
// CRITICAL: Check word boundaries - no letters immediately before/after
|
438 |
-
if (col > 0 && grid[row][col - 1] !== '.') {
|
439 |
-
return false; // Word would have a preceding letter
|
440 |
-
}
|
441 |
-
if (col + word.length < size && grid[row][col + word.length] !== '.') {
|
442 |
-
return false; // Word would have a trailing letter
|
443 |
-
}
|
444 |
-
|
445 |
-
// Check each letter position
|
446 |
-
for (let i = 0; i < word.length; i++) {
|
447 |
-
const currentCol = col + i;
|
448 |
-
const currentCell = grid[row][currentCol];
|
449 |
-
|
450 |
-
// If cell is occupied, it must match the letter we're placing
|
451 |
-
if (currentCell !== '.' && currentCell !== word[i]) {
|
452 |
-
return false;
|
453 |
-
}
|
454 |
-
|
455 |
-
// For empty cells, check perpendicular validity
|
456 |
-
if (currentCell === '.') {
|
457 |
-
// Check that placing this letter won't create invalid perpendicular words
|
458 |
-
if (!this.isValidPerpendicularPlacement(grid, word[i], row, currentCol, 'vertical')) {
|
459 |
-
return false;
|
460 |
-
}
|
461 |
-
}
|
462 |
-
}
|
463 |
-
} else { // vertical
|
464 |
-
// CRITICAL: Check word boundaries - no letters immediately before/after
|
465 |
-
if (row > 0 && grid[row - 1][col] !== '.') {
|
466 |
-
return false; // Word would have a preceding letter
|
467 |
-
}
|
468 |
-
if (row + word.length < size && grid[row + word.length][col] !== '.') {
|
469 |
-
return false; // Word would have a trailing letter
|
470 |
-
}
|
471 |
-
|
472 |
-
// Check each letter position
|
473 |
-
for (let i = 0; i < word.length; i++) {
|
474 |
-
const currentRow = row + i;
|
475 |
-
const currentCell = grid[currentRow][col];
|
476 |
-
|
477 |
-
// If cell is occupied, it must match the letter we're placing
|
478 |
-
if (currentCell !== '.' && currentCell !== word[i]) {
|
479 |
-
return false;
|
480 |
-
}
|
481 |
-
|
482 |
-
// For empty cells, check perpendicular validity
|
483 |
-
if (currentCell === '.') {
|
484 |
-
// Check that placing this letter won't create invalid perpendicular words
|
485 |
-
if (!this.isValidPerpendicularPlacement(grid, word[i], currentRow, col, 'horizontal')) {
|
486 |
-
return false;
|
487 |
-
}
|
488 |
-
}
|
489 |
-
}
|
490 |
-
}
|
491 |
-
|
492 |
-
return true;
|
493 |
-
}
|
494 |
-
|
495 |
-
placeWord(grid, word, row, col, direction) {
|
496 |
-
const originalState = [];
|
497 |
-
|
498 |
-
if (direction === 'horizontal') {
|
499 |
-
for (let i = 0; i < word.length; i++) {
|
500 |
-
originalState.push({
|
501 |
-
row: row,
|
502 |
-
col: col + i,
|
503 |
-
value: grid[row][col + i]
|
504 |
-
});
|
505 |
-
grid[row][col + i] = word[i];
|
506 |
-
}
|
507 |
-
} else {
|
508 |
-
for (let i = 0; i < word.length; i++) {
|
509 |
-
originalState.push({
|
510 |
-
row: row + i,
|
511 |
-
col: col,
|
512 |
-
value: grid[row + i][col]
|
513 |
-
});
|
514 |
-
grid[row + i][col] = word[i];
|
515 |
-
}
|
516 |
-
}
|
517 |
-
|
518 |
-
return originalState;
|
519 |
-
}
|
520 |
-
|
521 |
-
removeWord(grid, originalState) {
|
522 |
-
originalState.forEach(state => {
|
523 |
-
grid[state.row][state.col] = state.value;
|
524 |
-
});
|
525 |
-
}
|
526 |
-
|
527 |
-
generateClues(words, placedWords) {
|
528 |
-
return placedWords.map((placedWord, index) => {
|
529 |
-
// Find the word object that matches this placed word
|
530 |
-
const wordObj = words.find(w => w.word.toUpperCase() === placedWord.word);
|
531 |
-
|
532 |
-
return {
|
533 |
-
number: index + 1,
|
534 |
-
word: placedWord.word,
|
535 |
-
text: wordObj ? (wordObj.clue || `Clue for ${placedWord.word}`) : `Clue for ${placedWord.word}`,
|
536 |
-
direction: placedWord.direction === 'horizontal' ? 'across' : 'down',
|
537 |
-
position: { row: placedWord.row, col: placedWord.col }
|
538 |
-
};
|
539 |
-
});
|
540 |
-
}
|
541 |
-
|
542 |
-
static validateAnswers(puzzle, userAnswers) {
|
543 |
-
const correct = {};
|
544 |
-
const incorrect = {};
|
545 |
-
let totalCorrect = 0;
|
546 |
-
let totalCells = 0;
|
547 |
-
|
548 |
-
puzzle.grid.forEach((row, rowIndex) => {
|
549 |
-
row.forEach((cell, colIndex) => {
|
550 |
-
if (cell !== '.') {
|
551 |
-
totalCells++;
|
552 |
-
const key = `${rowIndex}-${colIndex}`;
|
553 |
-
const userAnswer = userAnswers[key];
|
554 |
-
|
555 |
-
if (userAnswer === cell) {
|
556 |
-
correct[key] = true;
|
557 |
-
totalCorrect++;
|
558 |
-
} else if (userAnswer) {
|
559 |
-
incorrect[key] = true;
|
560 |
-
}
|
561 |
-
}
|
562 |
-
});
|
563 |
-
});
|
564 |
-
|
565 |
-
return {
|
566 |
-
correct,
|
567 |
-
incorrect,
|
568 |
-
isComplete: totalCorrect === totalCells,
|
569 |
-
progress: totalCells > 0 ? (totalCorrect / totalCells) * 100 : 0
|
570 |
-
};
|
571 |
-
}
|
572 |
-
|
573 |
-
findAllIntersectionPlacements(grid, word, placedWords) {
|
574 |
-
const placements = [];
|
575 |
-
|
576 |
-
// For each placed word, find potential intersections
|
577 |
-
for (const placedWord of placedWords) {
|
578 |
-
const intersections = this.findWordIntersections(word, placedWord.word);
|
579 |
-
|
580 |
-
for (const intersection of intersections) {
|
581 |
-
const { wordPos, placedPos } = intersection;
|
582 |
-
|
583 |
-
// Calculate where the new word would be placed
|
584 |
-
const placementInfo = this.calculateIntersectionPlacement(
|
585 |
-
word, wordPos, placedWord, placedPos
|
586 |
-
);
|
587 |
-
|
588 |
-
if (placementInfo) {
|
589 |
-
// Calculate score for this placement
|
590 |
-
const score = this.calculatePlacementScore(grid, word, placementInfo, placedWords);
|
591 |
-
|
592 |
-
placements.push({
|
593 |
-
...placementInfo,
|
594 |
-
score,
|
595 |
-
intersectingWord: placedWord.word,
|
596 |
-
intersectionPoint: { wordPos, placedPos }
|
597 |
-
});
|
598 |
-
}
|
599 |
-
}
|
600 |
-
}
|
601 |
-
|
602 |
-
return placements;
|
603 |
-
}
|
604 |
-
|
605 |
-
calculatePlacementScore(grid, word, placement, placedWords) {
|
606 |
-
const { row, col, direction } = placement;
|
607 |
-
let score = 0;
|
608 |
-
|
609 |
-
// Base score for creating an intersection
|
610 |
-
score += 100;
|
611 |
-
|
612 |
-
// Bonus for multiple intersections with the same word
|
613 |
-
let intersectionCount = 0;
|
614 |
-
|
615 |
-
if (direction === 'horizontal') {
|
616 |
-
for (let i = 0; i < word.length; i++) {
|
617 |
-
const cellRow = row;
|
618 |
-
const cellCol = col + i;
|
619 |
-
|
620 |
-
if (cellCol >= 0 && cellCol < grid.length && grid[cellRow] && grid[cellRow][cellCol] === word[i]) {
|
621 |
-
intersectionCount++;
|
622 |
-
}
|
623 |
-
}
|
624 |
-
} else {
|
625 |
-
for (let i = 0; i < word.length; i++) {
|
626 |
-
const cellRow = row + i;
|
627 |
-
const cellCol = col;
|
628 |
-
|
629 |
-
if (cellRow >= 0 && cellRow < grid.length && grid[cellRow] && grid[cellRow][cellCol] === word[i]) {
|
630 |
-
intersectionCount++;
|
631 |
-
}
|
632 |
-
}
|
633 |
-
}
|
634 |
-
|
635 |
-
// Multiple intersections are MUCH better
|
636 |
-
score += intersectionCount * 200;
|
637 |
-
|
638 |
-
// Bonus for placing near the center
|
639 |
-
const center = Math.floor(grid.length / 2);
|
640 |
-
const distanceFromCenter = Math.abs(row - center) + Math.abs(col - center);
|
641 |
-
score -= distanceFromCenter * 5;
|
642 |
-
|
643 |
-
// Bonus for connecting to multiple existing words
|
644 |
-
const adjacentWords = this.countAdjacentWords(grid, word, row, col, direction, placedWords);
|
645 |
-
score += adjacentWords * 50;
|
646 |
-
|
647 |
-
// Prefer compact layouts - bonus for being close to existing words
|
648 |
-
const proximityBonus = this.calculateProximityBonus(grid, row, col, direction, word.length);
|
649 |
-
score += proximityBonus;
|
650 |
-
|
651 |
-
return score;
|
652 |
-
}
|
653 |
-
|
654 |
-
countAdjacentWords(grid, word, row, col, direction, placedWords) {
|
655 |
-
let adjacentCount = 0;
|
656 |
-
const size = grid.length;
|
657 |
-
|
658 |
-
if (direction === 'horizontal') {
|
659 |
-
for (let i = 0; i < word.length; i++) {
|
660 |
-
const cellRow = row;
|
661 |
-
const cellCol = col + i;
|
662 |
-
|
663 |
-
// Check boundaries before accessing array
|
664 |
-
if (cellRow < 0 || cellRow >= size || cellCol < 0 || cellCol >= size) continue;
|
665 |
-
|
666 |
-
// Check above and below
|
667 |
-
if (cellRow > 0 && grid[cellRow - 1] && grid[cellRow - 1][cellCol] && grid[cellRow - 1][cellCol] !== '.') {
|
668 |
-
adjacentCount++;
|
669 |
-
}
|
670 |
-
if (cellRow < size - 1 && grid[cellRow + 1] && grid[cellRow + 1][cellCol] && grid[cellRow + 1][cellCol] !== '.') {
|
671 |
-
adjacentCount++;
|
672 |
-
}
|
673 |
-
}
|
674 |
-
} else {
|
675 |
-
for (let i = 0; i < word.length; i++) {
|
676 |
-
const cellRow = row + i;
|
677 |
-
const cellCol = col;
|
678 |
-
|
679 |
-
// Check boundaries before accessing array
|
680 |
-
if (cellRow < 0 || cellRow >= size || cellCol < 0 || cellCol >= size) continue;
|
681 |
-
|
682 |
-
// Check left and right
|
683 |
-
if (cellCol > 0 && grid[cellRow] && grid[cellRow][cellCol - 1] && grid[cellRow][cellCol - 1] !== '.') {
|
684 |
-
adjacentCount++;
|
685 |
-
}
|
686 |
-
if (cellCol < size - 1 && grid[cellRow] && grid[cellRow][cellCol + 1] && grid[cellRow][cellCol + 1] !== '.') {
|
687 |
-
adjacentCount++;
|
688 |
-
}
|
689 |
-
}
|
690 |
-
}
|
691 |
-
|
692 |
-
return adjacentCount;
|
693 |
-
}
|
694 |
-
|
695 |
-
calculateProximityBonus(grid, row, col, direction, wordLength) {
|
696 |
-
let cellsNearOtherWords = 0;
|
697 |
-
const size = grid.length;
|
698 |
-
|
699 |
-
if (direction === 'horizontal') {
|
700 |
-
for (let i = 0; i < wordLength; i++) {
|
701 |
-
const cellCol = col + i;
|
702 |
-
|
703 |
-
// Check surrounding cells for letters
|
704 |
-
for (let dr = -1; dr <= 1; dr++) {
|
705 |
-
for (let dc = -1; dc <= 1; dc++) {
|
706 |
-
const checkRow = row + dr;
|
707 |
-
const checkCol = cellCol + dc;
|
708 |
-
|
709 |
-
if (checkRow >= 0 && checkRow < size &&
|
710 |
-
checkCol >= 0 && checkCol < size &&
|
711 |
-
grid[checkRow] && grid[checkRow][checkCol] &&
|
712 |
-
grid[checkRow][checkCol] !== '.') {
|
713 |
-
cellsNearOtherWords++;
|
714 |
-
}
|
715 |
-
}
|
716 |
-
}
|
717 |
-
}
|
718 |
-
} else {
|
719 |
-
for (let i = 0; i < wordLength; i++) {
|
720 |
-
const cellRow = row + i;
|
721 |
-
|
722 |
-
// Check surrounding cells for letters
|
723 |
-
for (let dr = -1; dr <= 1; dr++) {
|
724 |
-
for (let dc = -1; dc <= 1; dc++) {
|
725 |
-
const checkRow = cellRow + dr;
|
726 |
-
const checkCol = col + dc;
|
727 |
-
|
728 |
-
if (checkRow >= 0 && checkRow < size &&
|
729 |
-
checkCol >= 0 && checkCol < size &&
|
730 |
-
grid[checkRow] && grid[checkRow][checkCol] &&
|
731 |
-
grid[checkRow][checkCol] !== '.') {
|
732 |
-
cellsNearOtherWords++;
|
733 |
-
}
|
734 |
-
}
|
735 |
-
}
|
736 |
-
}
|
737 |
-
}
|
738 |
-
|
739 |
-
return cellsNearOtherWords * 10;
|
740 |
-
}
|
741 |
-
|
742 |
-
findIntersectionPlacements(grid, word, placedWords) {
|
743 |
-
return this.findAllIntersectionPlacements(grid, word, placedWords);
|
744 |
-
}
|
745 |
-
|
746 |
-
findWordIntersections(word1, word2) {
|
747 |
-
const intersections = [];
|
748 |
-
|
749 |
-
for (let i = 0; i < word1.length; i++) {
|
750 |
-
for (let j = 0; j < word2.length; j++) {
|
751 |
-
if (word1[i] === word2[j]) {
|
752 |
-
intersections.push({
|
753 |
-
wordPos: i, // Position in the new word
|
754 |
-
placedPos: j // Position in the already placed word
|
755 |
-
});
|
756 |
-
}
|
757 |
-
}
|
758 |
-
}
|
759 |
-
|
760 |
-
return intersections;
|
761 |
-
}
|
762 |
-
|
763 |
-
calculateIntersectionPlacement(newWord, newWordPos, placedWord, placedWordPos) {
|
764 |
-
const { row: placedRow, col: placedCol, direction: placedDirection } = placedWord;
|
765 |
-
|
766 |
-
// Calculate the position of the intersecting letter in the grid
|
767 |
-
let intersectionRow, intersectionCol;
|
768 |
-
|
769 |
-
if (placedDirection === 'horizontal') {
|
770 |
-
intersectionRow = placedRow;
|
771 |
-
intersectionCol = placedCol + placedWordPos;
|
772 |
-
} else {
|
773 |
-
intersectionRow = placedRow + placedWordPos;
|
774 |
-
intersectionCol = placedCol;
|
775 |
-
}
|
776 |
-
|
777 |
-
// Calculate where the new word should start
|
778 |
-
const newDirection = placedDirection === 'horizontal' ? 'vertical' : 'horizontal';
|
779 |
-
|
780 |
-
let newRow, newCol;
|
781 |
-
|
782 |
-
if (newDirection === 'horizontal') {
|
783 |
-
newRow = intersectionRow;
|
784 |
-
newCol = intersectionCol - newWordPos;
|
785 |
-
} else {
|
786 |
-
newRow = intersectionRow - newWordPos;
|
787 |
-
newCol = intersectionCol;
|
788 |
-
}
|
789 |
-
|
790 |
-
return {
|
791 |
-
row: newRow,
|
792 |
-
col: newCol,
|
793 |
-
direction: newDirection
|
794 |
-
};
|
795 |
-
}
|
796 |
-
|
797 |
-
findFallbackPlacements(grid, word, size) {
|
798 |
-
const placements = [];
|
799 |
-
|
800 |
-
// Generate some positions near the center
|
801 |
-
const center = Math.floor(size / 2);
|
802 |
-
const searchRadius = Math.min(3, Math.floor(size / 4));
|
803 |
-
|
804 |
-
for (let r = center - searchRadius; r <= center + searchRadius; r++) {
|
805 |
-
for (let c = center - searchRadius; c <= center + searchRadius; c++) {
|
806 |
-
if (r >= 0 && c >= 0 && r < size && c < size) {
|
807 |
-
for (const direction of ['horizontal', 'vertical']) {
|
808 |
-
placements.push({ row: r, col: c, direction });
|
809 |
-
}
|
810 |
-
}
|
811 |
-
}
|
812 |
-
}
|
813 |
-
|
814 |
-
return placements;
|
815 |
-
}
|
816 |
-
|
817 |
-
countIntersections(grid, word, row, col, direction) {
|
818 |
-
let intersections = 0;
|
819 |
-
|
820 |
-
if (direction === 'horizontal') {
|
821 |
-
for (let i = 0; i < word.length; i++) {
|
822 |
-
if (grid[row][col + i] === word[i]) {
|
823 |
-
intersections++;
|
824 |
-
}
|
825 |
-
}
|
826 |
-
} else {
|
827 |
-
for (let i = 0; i < word.length; i++) {
|
828 |
-
if (grid[row + i][col] === word[i]) {
|
829 |
-
intersections++;
|
830 |
-
}
|
831 |
-
}
|
832 |
-
}
|
833 |
-
|
834 |
-
return intersections;
|
835 |
-
}
|
836 |
-
|
837 |
-
wouldConnect(grid, word, row, col, direction, placedWords) {
|
838 |
-
if (placedWords.length === 0) return true; // First word always connects
|
839 |
-
|
840 |
-
// Check if this word would intersect with any placed word
|
841 |
-
if (direction === 'horizontal') {
|
842 |
-
for (let i = 0; i < word.length; i++) {
|
843 |
-
if (grid[row][col + i] !== '.') {
|
844 |
-
return true; // Intersects with existing letter
|
845 |
-
}
|
846 |
-
// Check if adjacent to existing letters (forming a connection)
|
847 |
-
if ((row > 0 && grid[row - 1][col + i] !== '.') ||
|
848 |
-
(row < grid.length - 1 && grid[row + 1][col + i] !== '.')) {
|
849 |
-
return true;
|
850 |
-
}
|
851 |
-
}
|
852 |
-
} else {
|
853 |
-
for (let i = 0; i < word.length; i++) {
|
854 |
-
if (grid[row + i][col] !== '.') {
|
855 |
-
return true; // Intersects with existing letter
|
856 |
-
}
|
857 |
-
// Check if adjacent to existing letters (forming a connection)
|
858 |
-
if ((col > 0 && grid[row + i][col - 1] !== '.') ||
|
859 |
-
(col < grid[0].length - 1 && grid[row + i][col + 1] !== '.')) {
|
860 |
-
return true;
|
861 |
-
}
|
862 |
-
}
|
863 |
-
}
|
864 |
-
|
865 |
-
return false;
|
866 |
-
}
|
867 |
-
|
868 |
-
isValidPerpendicularPlacement(grid, letter, row, col, checkDirection) {
|
869 |
-
const size = grid.length;
|
870 |
-
|
871 |
-
if (checkDirection === 'vertical') {
|
872 |
-
// Check if placing this letter would create an invalid vertical sequence
|
873 |
-
let hasAbove = row > 0 && grid[row - 1] && grid[row - 1][col] !== '.';
|
874 |
-
let hasBelow = row < size - 1 && grid[row + 1] && grid[row + 1][col] !== '.';
|
875 |
-
|
876 |
-
// STRICT: Don't allow this letter to be placed if it would extend an existing vertical word
|
877 |
-
// unless it's exactly at an intersection point with matching letters
|
878 |
-
if (hasAbove || hasBelow) {
|
879 |
-
// Only allow if this letter is exactly matching existing intersections
|
880 |
-
return grid[row][col] === letter;
|
881 |
-
}
|
882 |
-
} else { // horizontal
|
883 |
-
// Check if placing this letter would create an invalid horizontal sequence
|
884 |
-
let hasLeft = col > 0 && grid[row] && grid[row][col - 1] !== '.';
|
885 |
-
let hasRight = col < size - 1 && grid[row] && grid[row][col + 1] !== '.';
|
886 |
-
|
887 |
-
// STRICT: Don't allow this letter to be placed if it would extend an existing horizontal word
|
888 |
-
// unless it's exactly at an intersection point with matching letters
|
889 |
-
if (hasLeft || hasRight) {
|
890 |
-
// Only allow if this letter is exactly matching existing intersections
|
891 |
-
return grid[row][col] === letter;
|
892 |
-
}
|
893 |
-
}
|
894 |
-
|
895 |
-
return true;
|
896 |
-
}
|
897 |
-
|
898 |
-
isValidCrossing(grid, letter, row, col) {
|
899 |
-
// This method should only be called for exact intersections
|
900 |
-
// where the letter already exists in the grid
|
901 |
-
return grid[row] && grid[row][col] === letter;
|
902 |
-
}
|
903 |
-
|
904 |
-
createSimpleCross(words) {
|
905 |
-
// Find the best intersection between the two words
|
906 |
-
const word1 = words[0];
|
907 |
-
const word2 = words[1];
|
908 |
-
const intersections = this.findWordIntersections(word1, word2);
|
909 |
-
|
910 |
-
if (intersections.length === 0) {
|
911 |
-
console.error(`❌ No intersections found between ${word1} and ${word2}`);
|
912 |
-
return null;
|
913 |
-
}
|
914 |
-
|
915 |
-
// Use the first intersection
|
916 |
-
const intersection = intersections[0];
|
917 |
-
const size = Math.max(word1.length, word2.length) + 4;
|
918 |
-
const grid = Array(size).fill().map(() => Array(size).fill('.'));
|
919 |
-
|
920 |
-
// Place first word horizontally in center
|
921 |
-
const centerRow = Math.floor(size / 2);
|
922 |
-
const centerCol = Math.floor((size - word1.length) / 2);
|
923 |
-
|
924 |
-
for (let i = 0; i < word1.length; i++) {
|
925 |
-
grid[centerRow][centerCol + i] = word1[i];
|
926 |
-
}
|
927 |
-
|
928 |
-
// Place second word vertically at intersection
|
929 |
-
const intersectionCol = centerCol + intersection.wordPos;
|
930 |
-
const word2StartRow = centerRow - intersection.placedPos;
|
931 |
-
|
932 |
-
for (let i = 0; i < word2.length; i++) {
|
933 |
-
grid[word2StartRow + i][intersectionCol] = word2[i];
|
934 |
-
}
|
935 |
-
|
936 |
-
const placedWords = [
|
937 |
-
{ word: word1, row: centerRow, col: centerCol, direction: 'horizontal', number: 1 },
|
938 |
-
{ word: word2, row: word2StartRow, col: intersectionCol, direction: 'vertical', number: 2 }
|
939 |
-
];
|
940 |
-
|
941 |
-
const trimmed = this.trimGrid(grid, placedWords);
|
942 |
-
return { grid: trimmed.grid, size: trimmed.grid.length, placedWords: trimmed.placedWords };
|
943 |
-
}
|
944 |
-
|
945 |
-
shuffleArray(array) {
|
946 |
-
const shuffled = [...array];
|
947 |
-
for (let i = shuffled.length - 1; i > 0; i--) {
|
948 |
-
const j = Math.floor(Math.random() * (i + 1));
|
949 |
-
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
950 |
-
}
|
951 |
-
return shuffled;
|
952 |
-
}
|
953 |
-
|
954 |
-
logWordClueMapping(words, useAI, topics) {
|
955 |
-
const source = useAI ? 'AI-generated' : 'Static';
|
956 |
-
const topicsList = Array.isArray(topics) ? topics.join(', ') : topics;
|
957 |
-
console.log(`\n📝 Word->Clue Mapping (${source}) for topics: ${topicsList}`);
|
958 |
-
console.log('════════════════════════════════════════');
|
959 |
-
|
960 |
-
// Create a formatted table of word->clue mappings
|
961 |
-
const wordClueMap = {};
|
962 |
-
words.forEach(wordObj => {
|
963 |
-
const word = wordObj.word || wordObj;
|
964 |
-
const clue = wordObj.clue || 'No clue available';
|
965 |
-
wordClueMap[word] = clue;
|
966 |
-
});
|
967 |
-
|
968 |
-
// Sort by word length for better readability
|
969 |
-
const sortedWords = Object.keys(wordClueMap).sort((a, b) => a.length - b.length);
|
970 |
-
|
971 |
-
sortedWords.forEach((word, index) => {
|
972 |
-
const clue = wordClueMap[word];
|
973 |
-
const paddedWord = word.padEnd(15, ' ');
|
974 |
-
console.log(`${(index + 1).toString().padStart(2, ' ')}. ${paddedWord} -> ${clue}`);
|
975 |
-
});
|
976 |
-
|
977 |
-
console.log('════════════════════════════════════════');
|
978 |
-
console.log(`Total words: ${words.length}\n`);
|
979 |
-
}
|
980 |
-
}
|
981 |
-
|
982 |
-
module.exports = CrosswordGenerator;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/services/embeddingWordService.js
DELETED
@@ -1,635 +0,0 @@
|
|
1 |
-
const { HfInference } = require('@huggingface/inference');
|
2 |
-
const WordService = require('./wordService');
|
3 |
-
|
4 |
-
// Store original console methods
|
5 |
-
const originalLog = console.log;
|
6 |
-
const originalWarn = console.warn;
|
7 |
-
|
8 |
-
// Helper to suppress HF verbose messages
|
9 |
-
function suppressHFLogs(fn) {
|
10 |
-
return async (...args) => {
|
11 |
-
console.log = (...msgs) => {
|
12 |
-
const msg = msgs.join(' ');
|
13 |
-
if (!msg.includes('Defaulting to') && !msg.includes('Auto selected provider')) {
|
14 |
-
originalLog(...msgs);
|
15 |
-
}
|
16 |
-
};
|
17 |
-
console.warn = (...msgs) => {
|
18 |
-
const msg = msgs.join(' ');
|
19 |
-
if (!msg.includes('Defaulting to') && !msg.includes('Auto selected provider')) {
|
20 |
-
originalWarn(...msgs);
|
21 |
-
}
|
22 |
-
};
|
23 |
-
|
24 |
-
try {
|
25 |
-
const result = await fn(...args);
|
26 |
-
return result;
|
27 |
-
} finally {
|
28 |
-
console.log = originalLog;
|
29 |
-
console.warn = originalWarn;
|
30 |
-
}
|
31 |
-
};
|
32 |
-
}
|
33 |
-
|
34 |
-
class EmbeddingWordService {
|
35 |
-
constructor() {
|
36 |
-
this.hf = null;
|
37 |
-
this.isInitialized = false;
|
38 |
-
this.embeddingCache = new Map();
|
39 |
-
this.wordCache = new Map();
|
40 |
-
this.fallbackToStatic = process.env.FALLBACK_TO_STATIC === 'true';
|
41 |
-
this.maxWordsPerGeneration = parseInt(process.env.MAX_WORDS_PER_GENERATION) || 15;
|
42 |
-
this.similarityThreshold = parseFloat(process.env.WORD_SIMILARITY_THRESHOLD) || 0.65;
|
43 |
-
this.maxTopicWords = parseInt(process.env.MAX_TOPIC_WORDS) || 50;
|
44 |
-
|
45 |
-
// console.log(`🔧 EmbeddingWordService initialized with fallback: ${this.fallbackToStatic}`);
|
46 |
-
|
47 |
-
// Initialize HuggingFace client
|
48 |
-
this.initializeHF();
|
49 |
-
}
|
50 |
-
|
51 |
-
async initializeHF() {
|
52 |
-
try {
|
53 |
-
const apiKey = process.env.HUGGINGFACE_API_KEY;
|
54 |
-
if (!apiKey || apiKey === 'hf_xxxxxxxxxx') {
|
55 |
-
console.warn('⚠️ HuggingFace API key not configured, falling back to static words');
|
56 |
-
this.isInitialized = false;
|
57 |
-
return;
|
58 |
-
}
|
59 |
-
|
60 |
-
// Configure HuggingFace client with minimal logging
|
61 |
-
this.hf = new HfInference(apiKey, {
|
62 |
-
use_cache: true,
|
63 |
-
dont_load_model: false
|
64 |
-
});
|
65 |
-
|
66 |
-
// Test the connection with a simple embedding request
|
67 |
-
await this.testConnection();
|
68 |
-
this.isInitialized = true;
|
69 |
-
console.log('✅ HuggingFace Embedding Service initialized successfully');
|
70 |
-
|
71 |
-
} catch (error) {
|
72 |
-
console.error('❌ Failed to initialize HuggingFace service:', error.message);
|
73 |
-
this.isInitialized = false;
|
74 |
-
}
|
75 |
-
}
|
76 |
-
|
77 |
-
async testConnection() {
|
78 |
-
const testConnectionInternal = async () => {
|
79 |
-
return await this.hf.featureExtraction({
|
80 |
-
model: process.env.EMBEDDING_MODEL || 'sentence-transformers/all-MiniLM-L6-v2',
|
81 |
-
inputs: 'test'
|
82 |
-
});
|
83 |
-
};
|
84 |
-
|
85 |
-
try {
|
86 |
-
const testEmbedding = await suppressHFLogs(testConnectionInternal)();
|
87 |
-
|
88 |
-
if (!testEmbedding || testEmbedding.length === 0) {
|
89 |
-
throw new Error('Empty embedding response');
|
90 |
-
}
|
91 |
-
console.log(`✅ HF Embedding test successful - vector dimension: ${testEmbedding.length}`);
|
92 |
-
return true;
|
93 |
-
} catch (error) {
|
94 |
-
console.error('❌ HuggingFace connection test failed:', error.message);
|
95 |
-
throw error;
|
96 |
-
}
|
97 |
-
}
|
98 |
-
|
99 |
-
async getEmbedding(text) {
|
100 |
-
if (!this.isInitialized || !this.hf) {
|
101 |
-
throw new Error('HuggingFace service not initialized');
|
102 |
-
}
|
103 |
-
|
104 |
-
// Check cache first
|
105 |
-
const cacheKey = text.toLowerCase().trim();
|
106 |
-
if (this.embeddingCache.has(cacheKey)) {
|
107 |
-
return this.embeddingCache.get(cacheKey);
|
108 |
-
}
|
109 |
-
|
110 |
-
const getEmbeddingInternal = async () => {
|
111 |
-
return await this.hf.featureExtraction({
|
112 |
-
model: process.env.EMBEDDING_MODEL || 'sentence-transformers/all-MiniLM-L6-v2',
|
113 |
-
inputs: text
|
114 |
-
});
|
115 |
-
};
|
116 |
-
|
117 |
-
try {
|
118 |
-
const embedding = await suppressHFLogs(getEmbeddingInternal)();
|
119 |
-
|
120 |
-
// Cache the embedding
|
121 |
-
if (process.env.CACHE_EMBEDDINGS === 'true') {
|
122 |
-
this.embeddingCache.set(cacheKey, embedding);
|
123 |
-
|
124 |
-
// Limit cache size to prevent memory issues
|
125 |
-
if (this.embeddingCache.size > 1000) {
|
126 |
-
const firstKey = this.embeddingCache.keys().next().value;
|
127 |
-
this.embeddingCache.delete(firstKey);
|
128 |
-
}
|
129 |
-
}
|
130 |
-
|
131 |
-
return embedding;
|
132 |
-
|
133 |
-
} catch (error) {
|
134 |
-
console.error(`❌ Failed to get embedding for "${text}":`, error.message);
|
135 |
-
throw error;
|
136 |
-
}
|
137 |
-
}
|
138 |
-
|
139 |
-
calculateCosineSimilarity(vecA, vecB) {
|
140 |
-
if (!vecA || !vecB || vecA.length !== vecB.length) {
|
141 |
-
return 0;
|
142 |
-
}
|
143 |
-
|
144 |
-
let dotProduct = 0;
|
145 |
-
let normA = 0;
|
146 |
-
let normB = 0;
|
147 |
-
|
148 |
-
for (let i = 0; i < vecA.length; i++) {
|
149 |
-
dotProduct += vecA[i] * vecB[i];
|
150 |
-
normA += vecA[i] * vecA[i];
|
151 |
-
normB += vecB[i] * vecB[i];
|
152 |
-
}
|
153 |
-
|
154 |
-
if (normA === 0 || normB === 0) {
|
155 |
-
return 0;
|
156 |
-
}
|
157 |
-
|
158 |
-
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
159 |
-
}
|
160 |
-
|
161 |
-
async generateWordsForTopics(topics, difficulty = 'medium', targetCount = 12) {
|
162 |
-
try {
|
163 |
-
// If HF is not initialized, fall back to static words
|
164 |
-
if (!this.isInitialized && this.fallbackToStatic) {
|
165 |
-
console.log('🔄 Using static word fallback');
|
166 |
-
return await this.getStaticWordsForTopics(topics, difficulty, targetCount);
|
167 |
-
}
|
168 |
-
|
169 |
-
if (!this.isInitialized) {
|
170 |
-
throw new Error('HuggingFace service not available and fallback disabled');
|
171 |
-
}
|
172 |
-
|
173 |
-
console.log(`🔍 Generating words for topics: ${topics.join(', ')} (difficulty: ${difficulty})`);
|
174 |
-
|
175 |
-
// Get topic embeddings
|
176 |
-
const topicEmbeddings = await Promise.all(
|
177 |
-
topics.map(async (topic) => ({
|
178 |
-
topic,
|
179 |
-
embedding: await this.getEmbedding(topic)
|
180 |
-
}))
|
181 |
-
);
|
182 |
-
|
183 |
-
// Generate diverse word candidates
|
184 |
-
const candidates = await this.generateWordCandidates(topicEmbeddings, difficulty);
|
185 |
-
|
186 |
-
// Score and filter words for crossword suitability
|
187 |
-
const scoredWords = this.scoreWordsForCrossword(candidates);
|
188 |
-
|
189 |
-
// Select best words up to target count
|
190 |
-
const selectedWords = scoredWords.slice(0, targetCount);
|
191 |
-
|
192 |
-
// If we don't have enough words, supplement with static words
|
193 |
-
if (selectedWords.length < targetCount && this.fallbackToStatic) {
|
194 |
-
console.log(`🔄 Only found ${selectedWords.length} AI words, supplementing with static words`);
|
195 |
-
const staticWords = await this.getStaticWordsForTopics(topics, difficulty, targetCount - selectedWords.length);
|
196 |
-
selectedWords.push(...staticWords);
|
197 |
-
}
|
198 |
-
|
199 |
-
console.log(`✅ Generated ${selectedWords.length} words for crossword`);
|
200 |
-
return selectedWords.slice(0, targetCount);
|
201 |
-
|
202 |
-
} catch (error) {
|
203 |
-
console.error('❌ Error generating words with embeddings:', error.message);
|
204 |
-
|
205 |
-
if (this.fallbackToStatic) {
|
206 |
-
console.log('🔄 Falling back to static words due to error');
|
207 |
-
return await this.getStaticWordsForTopics(topics, difficulty, targetCount);
|
208 |
-
}
|
209 |
-
|
210 |
-
throw error;
|
211 |
-
}
|
212 |
-
}
|
213 |
-
|
214 |
-
async generateWordCandidates(topicEmbeddings, difficulty) {
|
215 |
-
const candidates = new Set();
|
216 |
-
|
217 |
-
// TRUE AI GENERATION: Generate words directly using text generation
|
218 |
-
for (const { topic } of topicEmbeddings) {
|
219 |
-
const generatedWords = await this.generateWordsForTopic(topic, difficulty);
|
220 |
-
generatedWords.forEach(wordObj => candidates.add(wordObj));
|
221 |
-
}
|
222 |
-
|
223 |
-
console.log(`🤖 Generated ${candidates.size} AI words for topics`);
|
224 |
-
|
225 |
-
// Only if AI generation completely fails, fall back to static words
|
226 |
-
if (candidates.size === 0) {
|
227 |
-
console.log(`⚠️ AI generation failed, falling back to static words`);
|
228 |
-
for (const { topic, embedding } of topicEmbeddings) {
|
229 |
-
const staticWords = await this.findSimilarWords(topic, embedding, difficulty);
|
230 |
-
staticWords.forEach(word => candidates.add({ word, clue: `Static clue for ${word}` }));
|
231 |
-
}
|
232 |
-
}
|
233 |
-
|
234 |
-
return Array.from(candidates);
|
235 |
-
}
|
236 |
-
|
237 |
-
async generateWordsForTopic(topic, difficulty) {
|
238 |
-
try {
|
239 |
-
// Use comprehensive topic-specific word banks instead of LLM generation
|
240 |
-
const topicWords = this.getTopicSpecificWords(topic, difficulty);
|
241 |
-
|
242 |
-
if (topicWords.length === 0) {
|
243 |
-
console.log(`⚠️ No words found for topic "${topic}", falling back to prompts`);
|
244 |
-
// Fallback to LLM generation if needed
|
245 |
-
return await this.generateWordsFromPrompts(topic, difficulty);
|
246 |
-
}
|
247 |
-
|
248 |
-
// Randomly select and shuffle words for variety
|
249 |
-
const shuffled = this.shuffleArray(topicWords);
|
250 |
-
const selected = shuffled.slice(0, Math.min(15, topicWords.length));
|
251 |
-
|
252 |
-
console.log(`🎯 Generated ${selected.length} topic-specific words for "${topic}"`);
|
253 |
-
return selected;
|
254 |
-
} catch (error) {
|
255 |
-
console.error(`❌ Failed to generate words for topic "${topic}":`, error.message);
|
256 |
-
return [];
|
257 |
-
}
|
258 |
-
}
|
259 |
-
|
260 |
-
getTopicSpecificWords(topic, difficulty) {
|
261 |
-
// Comprehensive topic-specific word banks - TRUE GENERATION!
|
262 |
-
const topicBanks = {
|
263 |
-
'Animals': {
|
264 |
-
easy: [
|
265 |
-
{ word: 'CAT', clue: 'Feline pet' },
|
266 |
-
{ word: 'DOG', clue: 'Canine companion' },
|
267 |
-
{ word: 'BIRD', clue: 'Flying creature' },
|
268 |
-
{ word: 'FISH', clue: 'Swimming creature' },
|
269 |
-
{ word: 'BEAR', clue: 'Large forest mammal' },
|
270 |
-
{ word: 'LION', clue: 'King of jungle' },
|
271 |
-
{ word: 'WOLF', clue: 'Pack hunter' },
|
272 |
-
{ word: 'DEER', clue: 'Antlered mammal' },
|
273 |
-
{ word: 'FROG', clue: 'Pond jumper' },
|
274 |
-
{ word: 'SNAKE', clue: 'Slithering reptile' }
|
275 |
-
],
|
276 |
-
medium: [
|
277 |
-
{ word: 'TIGER', clue: 'Striped big cat' },
|
278 |
-
{ word: 'WHALE', clue: 'Largest marine mammal' },
|
279 |
-
{ word: 'EAGLE', clue: 'Soaring predator' },
|
280 |
-
{ word: 'SHARK', clue: 'Ocean predator' },
|
281 |
-
{ word: 'ZEBRA', clue: 'Striped African animal' },
|
282 |
-
{ word: 'GIRAFFE', clue: 'Tallest mammal' },
|
283 |
-
{ word: 'ELEPHANT', clue: 'Largest land mammal' },
|
284 |
-
{ word: 'PENGUIN', clue: 'Antarctic bird' },
|
285 |
-
{ word: 'OCTOPUS', clue: 'Eight-armed sea creature' },
|
286 |
-
{ word: 'DOLPHIN', clue: 'Intelligent marine mammal' },
|
287 |
-
{ word: 'RABBIT', clue: 'Hopping mammal' },
|
288 |
-
{ word: 'TURTLE', clue: 'Shelled reptile' },
|
289 |
-
{ word: 'MONKEY', clue: 'Primate swinger' },
|
290 |
-
{ word: 'PARROT', clue: 'Colorful talking bird' }
|
291 |
-
],
|
292 |
-
hard: [
|
293 |
-
{ word: 'RHINOCEROS', clue: 'Horned thick-skinned mammal' },
|
294 |
-
{ word: 'HIPPOPOTAMUS', clue: 'River horse' },
|
295 |
-
{ word: 'CHIMPANZEE', clue: 'Human-like primate' },
|
296 |
-
{ word: 'ORANGUTAN', clue: 'Red-haired ape' },
|
297 |
-
{ word: 'CROCODILE', clue: 'Large aquatic reptile' },
|
298 |
-
{ word: 'CHAMELEON', clue: 'Color-changing lizard' },
|
299 |
-
{ word: 'FLAMINGO', clue: 'Pink wading bird' },
|
300 |
-
{ word: 'KANGAROO', clue: 'Hopping marsupial' },
|
301 |
-
{ word: 'PLATYPUS', clue: 'Egg-laying mammal' }
|
302 |
-
]
|
303 |
-
},
|
304 |
-
'Technology': {
|
305 |
-
easy: [
|
306 |
-
{ word: 'PHONE', clue: 'Mobile device' },
|
307 |
-
{ word: 'MOUSE', clue: 'Computer pointer' },
|
308 |
-
{ word: 'SCREEN', clue: 'Display surface' },
|
309 |
-
{ word: 'CABLE', clue: 'Connecting wire' },
|
310 |
-
{ word: 'VIRUS', clue: 'Malicious software' },
|
311 |
-
{ word: 'EMAIL', clue: 'Digital message' },
|
312 |
-
{ word: 'WIFI', clue: 'Wireless internet' },
|
313 |
-
{ word: 'CHIP', clue: 'Computer processor' }
|
314 |
-
],
|
315 |
-
medium: [
|
316 |
-
{ word: 'COMPUTER', clue: 'Electronic processor' },
|
317 |
-
{ word: 'KEYBOARD', clue: 'Input device with keys' },
|
318 |
-
{ word: 'MONITOR', clue: 'Computer display screen' },
|
319 |
-
{ word: 'SOFTWARE', clue: 'Computer programs' },
|
320 |
-
{ word: 'HARDWARE', clue: 'Physical components' },
|
321 |
-
{ word: 'DATABASE', clue: 'Organized data storage' },
|
322 |
-
{ word: 'NETWORK', clue: 'Connected systems' },
|
323 |
-
{ word: 'INTERNET', clue: 'Global network' },
|
324 |
-
{ word: 'BROWSER', clue: 'Web navigation tool' },
|
325 |
-
{ word: 'SERVER', clue: 'Data hosting computer' },
|
326 |
-
{ word: 'LAPTOP', clue: 'Portable computer' },
|
327 |
-
{ word: 'TABLET', clue: 'Touch screen device' },
|
328 |
-
{ word: 'ROUTER', clue: 'Network traffic director' },
|
329 |
-
{ word: 'PRINTER', clue: 'Document output device' }
|
330 |
-
],
|
331 |
-
hard: [
|
332 |
-
{ word: 'ALGORITHM', clue: 'Problem-solving procedure' },
|
333 |
-
{ word: 'CYBERSECURITY', clue: 'Digital protection field' },
|
334 |
-
{ word: 'BLOCKCHAIN', clue: 'Distributed ledger technology' },
|
335 |
-
{ word: 'ARTIFICIAL', clue: 'Man-made intelligence type' },
|
336 |
-
{ word: 'PROGRAMMING', clue: 'Code writing process' },
|
337 |
-
{ word: 'ENCRYPTION', clue: 'Data scrambling method' },
|
338 |
-
{ word: 'SEMICONDUCTOR', clue: 'Electronic component material' }
|
339 |
-
]
|
340 |
-
},
|
341 |
-
'Science': {
|
342 |
-
easy: [
|
343 |
-
{ word: 'ATOM', clue: 'Smallest unit of matter' },
|
344 |
-
{ word: 'GENE', clue: 'Heredity unit' },
|
345 |
-
{ word: 'ACID', clue: 'Chemical solution' },
|
346 |
-
{ word: 'LENS', clue: 'Light focusing tool' },
|
347 |
-
{ word: 'WAVE', clue: 'Energy transmission' },
|
348 |
-
{ word: 'MOON', clue: 'Earth\'s satellite' },
|
349 |
-
{ word: 'STAR', clue: 'Celestial light source' }
|
350 |
-
],
|
351 |
-
medium: [
|
352 |
-
{ word: 'MOLECULE', clue: 'Chemical compound unit' },
|
353 |
-
{ word: 'GRAVITY', clue: 'Attractive force' },
|
354 |
-
{ word: 'ELECTRON', clue: 'Negative particle' },
|
355 |
-
{ word: 'PROTEIN', clue: 'Complex biological molecule' },
|
356 |
-
{ word: 'CARBON', clue: 'Element in all life' },
|
357 |
-
{ word: 'OXYGEN', clue: 'Breathing gas' },
|
358 |
-
{ word: 'NEUTRON', clue: 'Neutral atomic particle' },
|
359 |
-
{ word: 'ENERGY', clue: 'Capacity to do work' },
|
360 |
-
{ word: 'GALAXY', clue: 'Star system collection' },
|
361 |
-
{ word: 'PLANET', clue: 'Orbiting celestial body' },
|
362 |
-
{ word: 'CRYSTAL', clue: 'Ordered solid structure' },
|
363 |
-
{ word: 'ENZYME', clue: 'Biological catalyst' }
|
364 |
-
],
|
365 |
-
hard: [
|
366 |
-
{ word: 'PHOTOSYNTHESIS', clue: 'Plant energy conversion' },
|
367 |
-
{ word: 'CHROMOSOME', clue: 'DNA carrying structure' },
|
368 |
-
{ word: 'THERMODYNAMICS', clue: 'Heat and energy study' },
|
369 |
-
{ word: 'ELECTROMAGNETIC', clue: 'Electric and magnetic field' },
|
370 |
-
{ word: 'QUANTUM', clue: 'Smallest energy unit' },
|
371 |
-
{ word: 'BIOCHEMISTRY', clue: 'Chemical life processes' }
|
372 |
-
]
|
373 |
-
},
|
374 |
-
'Geography': {
|
375 |
-
easy: [
|
376 |
-
{ word: 'HILL', clue: 'Small elevation' },
|
377 |
-
{ word: 'LAKE', clue: 'Body of water' },
|
378 |
-
{ word: 'RIVER', clue: 'Flowing water' },
|
379 |
-
{ word: 'OCEAN', clue: 'Large sea' },
|
380 |
-
{ word: 'ISLAND', clue: 'Land surrounded by water' },
|
381 |
-
{ word: 'BEACH', clue: 'Sandy shore' },
|
382 |
-
{ word: 'FOREST', clue: 'Dense tree area' }
|
383 |
-
],
|
384 |
-
medium: [
|
385 |
-
{ word: 'MOUNTAIN', clue: 'High elevation landform' },
|
386 |
-
{ word: 'VOLCANO', clue: 'Erupting mountain' },
|
387 |
-
{ word: 'DESERT', clue: 'Arid landscape' },
|
388 |
-
{ word: 'CANYON', clue: 'Deep valley' },
|
389 |
-
{ word: 'PLATEAU', clue: 'Elevated flatland' },
|
390 |
-
{ word: 'GLACIER', clue: 'Moving ice mass' },
|
391 |
-
{ word: 'PENINSULA', clue: 'Land jutting into water' },
|
392 |
-
{ word: 'CONTINENT', clue: 'Large landmass' },
|
393 |
-
{ word: 'ARCHIPELAGO', clue: 'Island chain' },
|
394 |
-
{ word: 'TUNDRA', clue: 'Arctic plains' },
|
395 |
-
{ word: 'SAVANNA', clue: 'Tropical grassland' },
|
396 |
-
{ word: 'ESTUARY', clue: 'River mouth' }
|
397 |
-
],
|
398 |
-
hard: [
|
399 |
-
{ word: 'TOPOGRAPHY', clue: 'Land surface features' },
|
400 |
-
{ word: 'CARTOGRAPHY', clue: 'Map making science' },
|
401 |
-
{ word: 'PRECIPITATION', clue: 'Weather moisture' },
|
402 |
-
{ word: 'CONTINENTAL', clue: 'Large landmass related' },
|
403 |
-
{ word: 'ECOSYSTEM', clue: 'Environmental system' }
|
404 |
-
]
|
405 |
-
}
|
406 |
-
};
|
407 |
-
|
408 |
-
const topicKey = Object.keys(topicBanks).find(key =>
|
409 |
-
key.toLowerCase() === topic.toLowerCase()
|
410 |
-
);
|
411 |
-
|
412 |
-
if (!topicKey) {
|
413 |
-
console.log(`⚠️ No word bank found for topic: ${topic}`);
|
414 |
-
return [];
|
415 |
-
}
|
416 |
-
|
417 |
-
const difficultyWords = topicBanks[topicKey][difficulty] || topicBanks[topicKey]['medium'] || [];
|
418 |
-
return difficultyWords;
|
419 |
-
}
|
420 |
-
|
421 |
-
async generateWordsFromPrompt(prompt, topic) {
|
422 |
-
const generateTextInternal = async () => {
|
423 |
-
return await this.hf.textGeneration({
|
424 |
-
model: 'microsoft/DialoGPT-medium',
|
425 |
-
inputs: prompt,
|
426 |
-
parameters: {
|
427 |
-
max_new_tokens: 150,
|
428 |
-
temperature: 0.8,
|
429 |
-
do_sample: true,
|
430 |
-
repetition_penalty: 1.2
|
431 |
-
}
|
432 |
-
});
|
433 |
-
};
|
434 |
-
|
435 |
-
try {
|
436 |
-
const response = await suppressHFLogs(generateTextInternal)();
|
437 |
-
const generatedText = response.generated_text || '';
|
438 |
-
|
439 |
-
// Extract words from the generated text
|
440 |
-
const words = this.extractWordsFromGeneration(generatedText, topic);
|
441 |
-
return words;
|
442 |
-
} catch (error) {
|
443 |
-
console.error(`❌ Text generation failed for prompt:`, error.message);
|
444 |
-
return [];
|
445 |
-
}
|
446 |
-
}
|
447 |
-
|
448 |
-
extractWordsFromGeneration(text, topic) {
|
449 |
-
const words = [];
|
450 |
-
|
451 |
-
// Extract potential words from the generated text
|
452 |
-
const lines = text.split('\n');
|
453 |
-
const wordPattern = /\b[A-Z]{3,15}\b/g;
|
454 |
-
|
455 |
-
for (const line of lines) {
|
456 |
-
const matches = line.match(wordPattern) || [];
|
457 |
-
for (const word of matches) {
|
458 |
-
if (this.isValidCrosswordWord(word)) {
|
459 |
-
words.push({
|
460 |
-
word: word.toUpperCase(),
|
461 |
-
clue: this.generateSimpleClue(word, topic)
|
462 |
-
});
|
463 |
-
}
|
464 |
-
}
|
465 |
-
}
|
466 |
-
|
467 |
-
// Remove duplicates and limit to reasonable number
|
468 |
-
const uniqueWords = Array.from(new Set(words.map(w => w.word)))
|
469 |
-
.slice(0, 10)
|
470 |
-
.map(word => words.find(w => w.word === word));
|
471 |
-
|
472 |
-
return uniqueWords;
|
473 |
-
}
|
474 |
-
|
475 |
-
isValidCrosswordWord(word) {
|
476 |
-
// Check if word is suitable for crossword
|
477 |
-
return (
|
478 |
-
word.length >= 3 &&
|
479 |
-
word.length <= 15 &&
|
480 |
-
/^[A-Z]+$/.test(word) &&
|
481 |
-
!['THE', 'AND', 'FOR', 'ARE', 'BUT', 'NOT', 'YOU', 'ALL'].includes(word)
|
482 |
-
);
|
483 |
-
}
|
484 |
-
|
485 |
-
generateSimpleClue(word, topic) {
|
486 |
-
// Generate basic clues based on word and topic
|
487 |
-
const clueTemplates = {
|
488 |
-
'Animals': `${word.toLowerCase()} (animal)`,
|
489 |
-
'Technology': `${word.toLowerCase()} (tech term)`,
|
490 |
-
'Science': `${word.toLowerCase()} (scientific term)`,
|
491 |
-
'Geography': `${word.toLowerCase()} (geographic feature)`
|
492 |
-
};
|
493 |
-
|
494 |
-
return clueTemplates[topic] || `${word.toLowerCase()} (${topic.toLowerCase()})`;
|
495 |
-
}
|
496 |
-
|
497 |
-
async findSimilarWords(topic, topicEmbedding, difficulty) {
|
498 |
-
const similarWords = [];
|
499 |
-
|
500 |
-
// Get static words for this topic as candidates
|
501 |
-
const staticWords = await WordService.getWordsByTopic(topic);
|
502 |
-
|
503 |
-
for (const wordObj of staticWords.slice(0, this.maxTopicWords)) {
|
504 |
-
try {
|
505 |
-
const word = wordObj.word;
|
506 |
-
const wordEmbedding = await this.getEmbedding(word);
|
507 |
-
const similarity = this.calculateCosineSimilarity(topicEmbedding, wordEmbedding);
|
508 |
-
|
509 |
-
if (similarity >= this.similarityThreshold) {
|
510 |
-
similarWords.push({
|
511 |
-
word: word,
|
512 |
-
clue: wordObj.clue,
|
513 |
-
similarity: similarity,
|
514 |
-
topic: topic
|
515 |
-
});
|
516 |
-
}
|
517 |
-
} catch (error) {
|
518 |
-
// Skip words that fail embedding generation
|
519 |
-
continue;
|
520 |
-
}
|
521 |
-
}
|
522 |
-
|
523 |
-
// Sort by similarity and return more top words for better variety
|
524 |
-
return similarWords
|
525 |
-
.sort((a, b) => b.similarity - a.similarity)
|
526 |
-
.slice(0, 20)
|
527 |
-
.map(item => item.word);
|
528 |
-
}
|
529 |
-
|
530 |
-
scoreWordsForCrossword(words) {
|
531 |
-
return words.map(wordItem => {
|
532 |
-
let score = 0;
|
533 |
-
|
534 |
-
// Handle both string words and word objects
|
535 |
-
const wordObj = typeof wordItem === 'string' ? { word: wordItem, clue: `Generated clue for ${wordItem}` } : wordItem;
|
536 |
-
const wordUpper = wordObj.word.toUpperCase();
|
537 |
-
|
538 |
-
// Length scoring (prefer 4-8 character words)
|
539 |
-
if (wordUpper.length >= 4 && wordUpper.length <= 8) {
|
540 |
-
score += 10;
|
541 |
-
} else if (wordUpper.length >= 3 && wordUpper.length <= 10) {
|
542 |
-
score += 5;
|
543 |
-
}
|
544 |
-
|
545 |
-
// Common letters bonus
|
546 |
-
const commonLetters = ['E', 'A', 'R', 'I', 'O', 'T', 'N', 'S'];
|
547 |
-
for (const letter of wordUpper) {
|
548 |
-
if (commonLetters.includes(letter)) {
|
549 |
-
score += 1;
|
550 |
-
}
|
551 |
-
}
|
552 |
-
|
553 |
-
// Vowel distribution
|
554 |
-
const vowels = ['A', 'E', 'I', 'O', 'U'];
|
555 |
-
const vowelCount = wordUpper.split('').filter(letter => vowels.includes(letter)).length;
|
556 |
-
score += vowelCount * 2;
|
557 |
-
|
558 |
-
// Avoid problematic characters
|
559 |
-
if (!/^[A-Z]+$/.test(wordUpper)) {
|
560 |
-
score -= 20; // Penalty for non-alphabetic characters
|
561 |
-
}
|
562 |
-
|
563 |
-
return {
|
564 |
-
word: wordUpper,
|
565 |
-
clue: wordObj.clue || `Generated clue for ${wordUpper}`,
|
566 |
-
score: score
|
567 |
-
};
|
568 |
-
})
|
569 |
-
.filter(item => item.score > 0) // Remove words with negative scores
|
570 |
-
.sort((a, b) => b.score - a.score); // Sort by score descending
|
571 |
-
}
|
572 |
-
|
573 |
-
async getStaticWordsForTopics(topics, difficulty, targetCount) {
|
574 |
-
try {
|
575 |
-
const allWords = [];
|
576 |
-
|
577 |
-
for (const topic of topics) {
|
578 |
-
const topicWords = await WordService.getWordsByTopic(topic);
|
579 |
-
allWords.push(...topicWords);
|
580 |
-
}
|
581 |
-
|
582 |
-
// Filter by difficulty
|
583 |
-
const filteredWords = this.filterWordsByDifficulty(allWords, difficulty);
|
584 |
-
|
585 |
-
// Shuffle and select target count
|
586 |
-
const shuffled = this.shuffleArray(filteredWords);
|
587 |
-
return shuffled.slice(0, targetCount);
|
588 |
-
|
589 |
-
} catch (error) {
|
590 |
-
console.error('❌ Error getting static words:', error.message);
|
591 |
-
throw error;
|
592 |
-
}
|
593 |
-
}
|
594 |
-
|
595 |
-
filterWordsByDifficulty(words, difficulty) {
|
596 |
-
const difficultyMap = {
|
597 |
-
easy: { minLen: 3, maxLen: 8 },
|
598 |
-
medium: { minLen: 4, maxLen: 10 },
|
599 |
-
hard: { minLen: 5, maxLen: 15 }
|
600 |
-
};
|
601 |
-
|
602 |
-
const { minLen, maxLen } = difficultyMap[difficulty] || difficultyMap.medium;
|
603 |
-
|
604 |
-
return words.filter(word =>
|
605 |
-
word.word.length >= minLen && word.word.length <= maxLen
|
606 |
-
);
|
607 |
-
}
|
608 |
-
|
609 |
-
shuffleArray(array) {
|
610 |
-
const shuffled = [...array];
|
611 |
-
for (let i = shuffled.length - 1; i > 0; i--) {
|
612 |
-
const j = Math.floor(Math.random() * (i + 1));
|
613 |
-
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
614 |
-
}
|
615 |
-
return shuffled;
|
616 |
-
}
|
617 |
-
|
618 |
-
// Utility methods for cache management
|
619 |
-
clearCache() {
|
620 |
-
this.embeddingCache.clear();
|
621 |
-
this.wordCache.clear();
|
622 |
-
console.log('🧹 Embedding and word caches cleared');
|
623 |
-
}
|
624 |
-
|
625 |
-
getCacheStats() {
|
626 |
-
return {
|
627 |
-
embeddingCacheSize: this.embeddingCache.size,
|
628 |
-
wordCacheSize: this.wordCache.size,
|
629 |
-
isInitialized: this.isInitialized,
|
630 |
-
fallbackEnabled: this.fallbackToStatic
|
631 |
-
};
|
632 |
-
}
|
633 |
-
}
|
634 |
-
|
635 |
-
module.exports = new EmbeddingWordService();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/services/wordService.js
DELETED
@@ -1,131 +0,0 @@
|
|
1 |
-
const fs = require('fs').promises;
|
2 |
-
const path = require('path');
|
3 |
-
|
4 |
-
class WordService {
|
5 |
-
constructor() {
|
6 |
-
this.wordsCache = new Map();
|
7 |
-
this.topicsCache = null;
|
8 |
-
}
|
9 |
-
|
10 |
-
async getAllTopics() {
|
11 |
-
if (this.topicsCache) {
|
12 |
-
return this.topicsCache;
|
13 |
-
}
|
14 |
-
|
15 |
-
try {
|
16 |
-
const dataDir = path.join(__dirname, '../../data/word-lists');
|
17 |
-
const files = await fs.readdir(dataDir);
|
18 |
-
|
19 |
-
const topics = files
|
20 |
-
.filter(file => file.endsWith('.json'))
|
21 |
-
.map(file => ({
|
22 |
-
id: file.replace('.json', ''),
|
23 |
-
name: this.formatTopicName(file.replace('.json', ''))
|
24 |
-
}));
|
25 |
-
|
26 |
-
this.topicsCache = topics;
|
27 |
-
return topics;
|
28 |
-
} catch (error) {
|
29 |
-
console.error('Error loading topics:', error);
|
30 |
-
return this.getDefaultTopics();
|
31 |
-
}
|
32 |
-
}
|
33 |
-
|
34 |
-
async getWordsByTopic(topicName) {
|
35 |
-
const cacheKey = topicName.toLowerCase();
|
36 |
-
|
37 |
-
if (this.wordsCache.has(cacheKey)) {
|
38 |
-
return this.wordsCache.get(cacheKey);
|
39 |
-
}
|
40 |
-
|
41 |
-
try {
|
42 |
-
const filePath = path.join(__dirname, '../../data/word-lists', `${topicName.toLowerCase()}.json`);
|
43 |
-
const fileContent = await fs.readFile(filePath, 'utf8');
|
44 |
-
const words = JSON.parse(fileContent);
|
45 |
-
|
46 |
-
this.wordsCache.set(cacheKey, words);
|
47 |
-
return words;
|
48 |
-
} catch (error) {
|
49 |
-
console.error(`Error loading words for topic ${topicName}:`, error);
|
50 |
-
return this.getDefaultWords(topicName);
|
51 |
-
}
|
52 |
-
}
|
53 |
-
|
54 |
-
formatTopicName(fileName) {
|
55 |
-
return fileName
|
56 |
-
.split('-')
|
57 |
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
58 |
-
.join(' ');
|
59 |
-
}
|
60 |
-
|
61 |
-
getDefaultTopics() {
|
62 |
-
return [
|
63 |
-
{ id: 'animals', name: 'Animals' },
|
64 |
-
{ id: 'geography', name: 'Geography' },
|
65 |
-
{ id: 'science', name: 'Science' },
|
66 |
-
{ id: 'technology', name: 'Technology' },
|
67 |
-
{ id: 'sports', name: 'Sports' },
|
68 |
-
{ id: 'history', name: 'History' },
|
69 |
-
{ id: 'food', name: 'Food' },
|
70 |
-
{ id: 'entertainment', name: 'Entertainment' },
|
71 |
-
{ id: 'nature', name: 'Nature' },
|
72 |
-
{ id: 'transportation', name: 'Transportation' },
|
73 |
-
{ id: 'art', name: 'Art' },
|
74 |
-
{ id: 'medicine', name: 'Medicine' }
|
75 |
-
];
|
76 |
-
}
|
77 |
-
|
78 |
-
getDefaultWords(topic) {
|
79 |
-
const defaultWordSets = {
|
80 |
-
animals: [
|
81 |
-
{ word: 'DOG', clue: 'Man\'s best friend' },
|
82 |
-
{ word: 'CAT', clue: 'Feline pet' },
|
83 |
-
{ word: 'ELEPHANT', clue: 'Large mammal with trunk' },
|
84 |
-
{ word: 'TIGER', clue: 'Striped big cat' },
|
85 |
-
{ word: 'WHALE', clue: 'Largest marine mammal' },
|
86 |
-
{ word: 'BUTTERFLY', clue: 'Colorful flying insect' },
|
87 |
-
{ word: 'BIRD', clue: 'Flying creature with feathers' },
|
88 |
-
{ word: 'FISH', clue: 'Aquatic animal with gills' }
|
89 |
-
],
|
90 |
-
science: [
|
91 |
-
{ word: 'ATOM', clue: 'Smallest unit of matter' },
|
92 |
-
{ word: 'GRAVITY', clue: 'Force that pulls objects down' },
|
93 |
-
{ word: 'MOLECULE', clue: 'Group of atoms bonded together' },
|
94 |
-
{ word: 'PHOTON', clue: 'Particle of light' },
|
95 |
-
{ word: 'CHEMISTRY', clue: 'Study of matter and reactions' },
|
96 |
-
{ word: 'PHYSICS', clue: 'Study of matter and energy' },
|
97 |
-
{ word: 'BIOLOGY', clue: 'Study of living organisms' },
|
98 |
-
{ word: 'ELEMENT', clue: 'Pure chemical substance' }
|
99 |
-
],
|
100 |
-
geography: [
|
101 |
-
{ word: 'MOUNTAIN', clue: 'High elevation landform' },
|
102 |
-
{ word: 'OCEAN', clue: 'Large body of salt water' },
|
103 |
-
{ word: 'DESERT', clue: 'Dry, arid region' },
|
104 |
-
{ word: 'CONTINENT', clue: 'Large landmass' },
|
105 |
-
{ word: 'RIVER', clue: 'Flowing body of water' },
|
106 |
-
{ word: 'ISLAND', clue: 'Land surrounded by water' },
|
107 |
-
{ word: 'FOREST', clue: 'Dense area of trees' },
|
108 |
-
{ word: 'VALLEY', clue: 'Low area between hills' }
|
109 |
-
],
|
110 |
-
technology: [
|
111 |
-
{ word: 'COMPUTER', clue: 'Electronic processing device' },
|
112 |
-
{ word: 'INTERNET', clue: 'Global computer network' },
|
113 |
-
{ word: 'ALGORITHM', clue: 'Set of rules for solving problems' },
|
114 |
-
{ word: 'DATABASE', clue: 'Organized collection of data' },
|
115 |
-
{ word: 'SOFTWARE', clue: 'Computer programs' },
|
116 |
-
{ word: 'ROBOT', clue: 'Automated machine' },
|
117 |
-
{ word: 'NETWORK', clue: 'Connected system of computers' },
|
118 |
-
{ word: 'CODE', clue: 'Programming instructions' }
|
119 |
-
]
|
120 |
-
};
|
121 |
-
|
122 |
-
return defaultWordSets[topic.toLowerCase()] || [];
|
123 |
-
}
|
124 |
-
|
125 |
-
clearCache() {
|
126 |
-
this.wordsCache.clear();
|
127 |
-
this.topicsCache = null;
|
128 |
-
}
|
129 |
-
}
|
130 |
-
|
131 |
-
module.exports = new WordService();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/test-ai-integration.js
DELETED
@@ -1,116 +0,0 @@
|
|
1 |
-
#!/usr/bin/env node
|
2 |
-
|
3 |
-
// Test script to demonstrate AI integration capabilities
|
4 |
-
require('dotenv').config();
|
5 |
-
|
6 |
-
const axios = require('axios').default || require('axios');
|
7 |
-
|
8 |
-
const BASE_URL = 'http://localhost:3001/api';
|
9 |
-
|
10 |
-
async function testAIIntegration() {
|
11 |
-
console.log('🧪 Testing AI Integration - Phase 6.1 Complete!\n');
|
12 |
-
|
13 |
-
try {
|
14 |
-
// Test 1: Check AI service status
|
15 |
-
console.log('1️⃣ Testing AI Service Status...');
|
16 |
-
const statusResponse = await axios.get(`${BASE_URL}/ai/status`);
|
17 |
-
const status = statusResponse.data;
|
18 |
-
|
19 |
-
console.log(` ✅ AI Service Status: ${status.status}`);
|
20 |
-
console.log(` 🤖 HF Initialized: ${status.aiService.initialized}`);
|
21 |
-
console.log(` 🔄 Fallback Enabled: ${status.aiService.fallbackEnabled}`);
|
22 |
-
console.log(` 📊 Cache Size: ${status.aiService.cacheStats.embeddingCache} embeddings\n`);
|
23 |
-
|
24 |
-
// Test 2: AI Word Generation
|
25 |
-
console.log('2️⃣ Testing AI Word Generation...');
|
26 |
-
const wordsResponse = await axios.post(`${BASE_URL}/ai/generate-words`, {
|
27 |
-
topics: ['animals', 'science'],
|
28 |
-
difficulty: 'medium',
|
29 |
-
count: 8
|
30 |
-
});
|
31 |
-
|
32 |
-
const wordsData = wordsResponse.data;
|
33 |
-
console.log(` ✅ Generated ${wordsData.generatedCount} words`);
|
34 |
-
console.log(` 🧠 AI Generated: ${wordsData.aiGenerated}`);
|
35 |
-
console.log(` 📝 Sample words:`, wordsData.words.slice(0, 3).map(w => w.word).join(', '));
|
36 |
-
console.log();
|
37 |
-
|
38 |
-
// Test 3: Enhanced Puzzle Generation (Static)
|
39 |
-
console.log('3️⃣ Testing Enhanced Puzzle Generation (Static)...');
|
40 |
-
const puzzleStaticResponse = await axios.post(`${BASE_URL}/generate`, {
|
41 |
-
topics: ['technology'],
|
42 |
-
difficulty: 'medium',
|
43 |
-
useAI: false
|
44 |
-
});
|
45 |
-
|
46 |
-
const puzzleStatic = puzzleStaticResponse.data;
|
47 |
-
console.log(` ✅ Generated puzzle with ${puzzleStatic.metadata.wordCount} words`);
|
48 |
-
console.log(` 🧠 AI Generated: ${puzzleStatic.metadata.aiGenerated}`);
|
49 |
-
console.log(` 📐 Grid Size: ${puzzleStatic.metadata.size}x${puzzleStatic.metadata.size}`);
|
50 |
-
console.log();
|
51 |
-
|
52 |
-
// Test 4: Enhanced Puzzle Generation (AI Fallback)
|
53 |
-
console.log('4️⃣ Testing Enhanced Puzzle Generation (AI with Fallback)...');
|
54 |
-
const puzzleAIResponse = await axios.post(`${BASE_URL}/generate`, {
|
55 |
-
topics: ['geography'],
|
56 |
-
difficulty: 'medium',
|
57 |
-
useAI: true
|
58 |
-
});
|
59 |
-
|
60 |
-
const puzzleAI = puzzleAIResponse.data;
|
61 |
-
console.log(` ✅ Generated puzzle with ${puzzleAI.metadata.wordCount} words`);
|
62 |
-
console.log(` 🧠 AI Generated: ${puzzleAI.metadata.aiGenerated}`);
|
63 |
-
console.log(` 📐 Grid Size: ${puzzleAI.metadata.size}x${puzzleAI.metadata.size}`);
|
64 |
-
console.log();
|
65 |
-
|
66 |
-
// Test 5: Performance Comparison
|
67 |
-
console.log('5️⃣ Performance Test...');
|
68 |
-
const startTime = Date.now();
|
69 |
-
|
70 |
-
await axios.post(`${BASE_URL}/ai/generate-words`, {
|
71 |
-
topics: ['animals'],
|
72 |
-
difficulty: 'easy',
|
73 |
-
count: 6
|
74 |
-
});
|
75 |
-
|
76 |
-
const endTime = Date.now();
|
77 |
-
console.log(` ⚡ Word generation took: ${endTime - startTime}ms`);
|
78 |
-
console.log();
|
79 |
-
|
80 |
-
console.log('✅ All AI Integration Tests Passed!\n');
|
81 |
-
|
82 |
-
console.log('📋 Phase 6.1 Summary:');
|
83 |
-
console.log(' ✅ HuggingFace dependencies installed');
|
84 |
-
console.log(' ✅ Environment variables configured');
|
85 |
-
console.log(' ✅ EmbeddingWordService class created');
|
86 |
-
console.log(' ✅ Graceful fallback to static words');
|
87 |
-
console.log(' ✅ New AI endpoints working');
|
88 |
-
console.log(' ✅ Enhanced puzzle generation');
|
89 |
-
console.log(' ✅ Performance and error handling');
|
90 |
-
console.log();
|
91 |
-
|
92 |
-
console.log('🚀 Next Steps (Phase 6.2):');
|
93 |
-
console.log(' 1. Sign up for HuggingFace account');
|
94 |
-
console.log(' 2. Get API token and update HUGGINGFACE_API_KEY');
|
95 |
-
console.log(' 3. Test real AI-powered word generation');
|
96 |
-
console.log(' 4. Implement dynamic clue generation');
|
97 |
-
console.log(' 5. Add Redis caching layer');
|
98 |
-
|
99 |
-
} catch (error) {
|
100 |
-
console.error('❌ Test failed:', error.message);
|
101 |
-
if (error.response) {
|
102 |
-
console.error(' Status:', error.response.status);
|
103 |
-
console.error(' Data:', error.response.data);
|
104 |
-
}
|
105 |
-
console.error('\n💡 Make sure the server is running on port 3001');
|
106 |
-
console.error(' Run: npm run dev');
|
107 |
-
}
|
108 |
-
}
|
109 |
-
|
110 |
-
// Check if axios is available
|
111 |
-
if (typeof axios === 'undefined') {
|
112 |
-
console.log('⚠️ axios not installed - install with: npm install axios');
|
113 |
-
console.log(' or test manually with curl commands');
|
114 |
-
} else {
|
115 |
-
testAIIntegration();
|
116 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossword-app/backend/src/test-embedding.js
DELETED
@@ -1,63 +0,0 @@
|
|
1 |
-
#!/usr/bin/env node
|
2 |
-
|
3 |
-
// Test script for EmbeddingWordService
|
4 |
-
require('dotenv').config();
|
5 |
-
const EmbeddingWordService = require('./services/embeddingWordService');
|
6 |
-
|
7 |
-
async function testEmbeddingService() {
|
8 |
-
console.log('🧪 Testing EmbeddingWordService...\n');
|
9 |
-
|
10 |
-
try {
|
11 |
-
// Test 1: Check service initialization
|
12 |
-
console.log('1️⃣ Testing service initialization...');
|
13 |
-
const stats = EmbeddingWordService.getCacheStats();
|
14 |
-
console.log(' Service stats:', stats);
|
15 |
-
|
16 |
-
if (stats.isInitialized) {
|
17 |
-
console.log(' ✅ HuggingFace service initialized successfully');
|
18 |
-
} else {
|
19 |
-
console.log(' ⚠️ HuggingFace service not initialized (likely missing API key)');
|
20 |
-
console.log(' 📝 This is expected if HUGGINGFACE_API_KEY is not set');
|
21 |
-
}
|
22 |
-
|
23 |
-
// Test 2: Generate words for topics
|
24 |
-
console.log('\n2️⃣ Testing word generation...');
|
25 |
-
const topics = ['animals', 'science'];
|
26 |
-
const words = await EmbeddingWordService.generateWordsForTopics(topics, 'medium', 8);
|
27 |
-
|
28 |
-
console.log(` Generated ${words.length} words for topics: ${topics.join(', ')}`);
|
29 |
-
words.forEach((word, index) => {
|
30 |
-
console.log(` ${index + 1}. ${word.word} - "${word.clue}"`);
|
31 |
-
});
|
32 |
-
|
33 |
-
// Test 3: Test different difficulties
|
34 |
-
console.log('\n3️⃣ Testing different difficulties...');
|
35 |
-
const difficulties = ['easy', 'medium', 'hard'];
|
36 |
-
|
37 |
-
for (const difficulty of difficulties) {
|
38 |
-
const diffWords = await EmbeddingWordService.generateWordsForTopics(['technology'], difficulty, 3);
|
39 |
-
console.log(` ${difficulty.toUpperCase()}: ${diffWords.map(w => w.word).join(', ')}`);
|
40 |
-
}
|
41 |
-
|
42 |
-
// Test 4: Cache stats
|
43 |
-
console.log('\n4️⃣ Final cache stats...');
|
44 |
-
const finalStats = EmbeddingWordService.getCacheStats();
|
45 |
-
console.log(' Final stats:', finalStats);
|
46 |
-
|
47 |
-
console.log('\n✅ All tests completed successfully!');
|
48 |
-
|
49 |
-
// Instructions for next steps
|
50 |
-
console.log('\n📋 Next Steps:');
|
51 |
-
console.log(' 1. Sign up for HuggingFace account at https://huggingface.co/join');
|
52 |
-
console.log(' 2. Generate API token at https://huggingface.co/settings/tokens');
|
53 |
-
console.log(' 3. Update HUGGINGFACE_API_KEY in .env file');
|
54 |
-
console.log(' 4. Run this test again to verify AI-powered word generation');
|
55 |
-
|
56 |
-
} catch (error) {
|
57 |
-
console.error('❌ Test failed:', error.message);
|
58 |
-
console.error(' Stack trace:', error.stack);
|
59 |
-
}
|
60 |
-
}
|
61 |
-
|
62 |
-
// Run the test
|
63 |
-
testEmbeddingService();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|