LukasBe commited on
Commit
647c264
Β·
verified Β·
1 Parent(s): febd64a

Add 2 files

Browse files
Files changed (2) hide show
  1. index.html +608 -72
  2. prompts.txt +6 -1
index.html CHANGED
@@ -3,95 +3,322 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Tower Text Twist</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
 
 
 
 
 
 
 
10
  .letter-tile {
11
- transition: all 0.2s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  }
 
 
 
 
 
13
  .letter-tile.selected {
14
- transform: translateY(-5px);
15
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
 
 
16
  }
 
17
  .feedback-message {
18
- animation: fadeOut 1.5s ease-out 1.5s forwards;
19
  }
20
- @keyframes fadeOut {
21
- to { opacity: 0; }
 
 
 
 
22
  }
 
23
  .timer-pulse {
24
- animation: pulse 1s infinite;
25
  }
 
26
  @keyframes pulse {
27
  0%, 100% { transform: scale(1); }
28
- 50% { transform: scale(1.05); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
  </style>
31
  </head>
32
- <body class="bg-gray-100 min-h-screen flex items-center justify-center p-4">
33
- <div class="max-w-md w-full bg-white rounded-2xl shadow-xl overflow-hidden">
34
  <!-- Game Header -->
35
- <div class="bg-indigo-600 p-4 text-white">
36
- <h1 class="text-2xl font-bold text-center">Tower Text Twist</h1>
37
- <p class="text-center text-indigo-100">Make words to build your tower!</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  </div>
39
 
40
  <!-- Game Info -->
41
- <div class="p-4 bg-indigo-50 flex justify-between items-center">
42
  <div class="text-center">
43
- <p class="text-xs text-indigo-600 font-semibold">SCORE</p>
44
- <p id="score" class="text-2xl font-bold text-indigo-800">0</p>
45
  </div>
46
  <div class="text-center">
47
- <p class="text-xs text-indigo-600 font-semibold">HEIGHT</p>
48
- <p id="height" class="text-2xl font-bold text-indigo-800">0</p>
49
  </div>
50
- <div class="text-center">
51
- <p class="text-xs text-indigo-600 font-semibold">TIME</p>
52
- <p id="timer" class="text-2xl font-bold text-red-600 timer-pulse">60</p>
 
 
 
53
  </div>
54
- </div>
55
 
56
  <!-- Feedback Message -->
57
- <div id="feedback" class="h-10 flex items-center justify-center">
58
  <p id="feedback-message" class="text-lg font-semibold opacity-0"></p>
59
  </div>
60
 
61
  <!-- Letter Tiles -->
62
- <div id="letter-area" class="p-4 grid grid-cols-6 gap-2">
63
  <!-- Letter tiles will be generated here -->
64
  </div>
65
 
66
  <!-- Current Word -->
67
- <div class="px-4 py-2 bg-gray-100">
68
- <div class="bg-white rounded-lg p-3 shadow-inner">
69
- <p class="text-xs text-gray-500">Current Word:</p>
70
- <p id="current-word" class="text-2xl font-mono text-center min-h-8">-</p>
 
 
 
71
  </div>
72
  </div>
73
 
74
  <!-- Controls -->
75
- <div class="p-4 flex gap-2">
76
- <button id="submit-btn" class="flex-1 bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed" disabled>
77
  <i class="fas fa-check mr-2"></i> Submit
78
  </button>
79
- <button id="clear-btn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-3 px-4 rounded-lg transition">
80
  <i class="fas fa-eraser mr-2"></i> Clear
81
  </button>
82
  </div>
83
 
84
  <!-- Found Words -->
85
- <div class="p-4 bg-gray-50 border-t">
86
- <p class="text-xs text-gray-500 mb-1">Found Words (<span id="found-count">0</span>):</p>
87
- <div id="found-words" class="flex flex-wrap gap-1">
 
 
 
 
 
88
  <!-- Found words will appear here -->
89
  </div>
90
  </div>
91
 
92
  <!-- Start Button -->
93
- <div class="p-4 bg-white">
94
- <button id="start-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition">
95
  <i class="fas fa-play mr-2"></i> Start Game
96
  </button>
97
  </div>
@@ -101,12 +328,20 @@
101
  // Game configuration
102
  const config = {
103
  roundTime: 60,
104
- availableLetters: ['A', 'E', 'S', 'T', 'R', 'N'],
105
  validWords: [
106
  "ART", "EAT", "NET", "RAT", "RENT", "STAR", "START", "TAN", "TEA", "TEN",
107
  "NEST", "RATE", "REST", "SAT", "SEA", "SENT", "SET", "TERN", "EARN", "EAST",
108
  "EATS", "NEAT", "RANT", "SEAT", "STAR", "TEAR", "TENS", "ANTS", "ARTS", "ERAS",
109
- "NATS", "NEAR", "NEST", "RATS", "SANE", "TARE", "TARN", "TARS", "TEAS", "TENS"
 
 
 
 
 
 
 
 
110
  ]
111
  };
112
 
@@ -118,7 +353,9 @@
118
  height: 0,
119
  selectedLetters: [],
120
  foundWords: [],
121
- timerInterval: null
 
 
122
  };
123
 
124
  // DOM elements
@@ -133,7 +370,11 @@
133
  clearBtn: document.getElementById('clear-btn'),
134
  startBtn: document.getElementById('start-btn'),
135
  foundWords: document.getElementById('found-words'),
136
- foundCount: document.getElementById('found-count')
 
 
 
 
137
  };
138
 
139
  // Initialize the game
@@ -142,10 +383,16 @@
142
  elements.letterArea.innerHTML = '';
143
  config.availableLetters.forEach(letter => {
144
  const tile = document.createElement('div');
145
- tile.className = 'letter-tile bg-white rounded-lg shadow-md flex items-center justify-center text-2xl font-bold cursor-pointer h-12';
146
  tile.textContent = letter;
147
  tile.dataset.letter = letter;
148
- tile.addEventListener('click', () => toggleLetter(letter, tile));
 
 
 
 
 
 
149
  elements.letterArea.appendChild(tile);
150
  });
151
 
@@ -156,11 +403,257 @@
156
  state.height = 0;
157
  state.selectedLetters = [];
158
  state.foundWords = [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  // Update UI
161
  updateUI();
162
  }
163
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  // Start a new round
165
  function startRound() {
166
  if (state.timerInterval) clearInterval(state.timerInterval);
@@ -176,16 +669,38 @@
176
  if (state.timer <= 0) {
177
  endRound();
178
  }
 
 
 
 
 
179
  }, 1000);
180
 
181
- showFeedback("Make words with the letters!", 2000);
182
  }
183
 
184
  // End the current round
185
  function endRound() {
186
  state.gameActive = false;
187
  clearInterval(state.timerInterval);
188
- showFeedback(`Round over! Score: ${state.score}`, 3000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  }
190
 
191
  // Toggle letter selection
@@ -197,14 +712,14 @@
197
  if (index === -1) {
198
  // Select the letter
199
  state.selectedLetters.push(letter);
200
- tile.classList.add('selected', 'bg-indigo-100', 'text-indigo-800');
201
  } else {
202
  // Deselect the letter (remove last occurrence)
203
  const lastIndex = state.selectedLetters.lastIndexOf(letter);
204
  if (lastIndex !== -1) {
205
  state.selectedLetters.splice(lastIndex, 1);
206
  }
207
- tile.classList.remove('selected', 'bg-indigo-100', 'text-indigo-800');
208
  }
209
 
210
  updateUI();
@@ -216,7 +731,7 @@
216
 
217
  state.selectedLetters = [];
218
  document.querySelectorAll('.letter-tile').forEach(tile => {
219
- tile.classList.remove('selected', 'bg-indigo-100', 'text-indigo-800');
220
  });
221
 
222
  updateUI();
@@ -225,58 +740,68 @@
225
  // Submit current word
226
  function submitWord() {
227
  if (!state.gameActive || state.selectedLetters.length < 3) {
228
- showFeedback(state.selectedLetters.length < 3 ? "Word too short!" : "Game not active", 1500);
229
  return;
230
  }
231
 
232
- const word = state.selectedLetters.join('');
233
 
234
  if (state.foundWords.includes(word)) {
235
- showFeedback("Already found!", 1500);
236
  } else if (config.validWords.includes(word)) {
237
  // Valid word
238
  state.foundWords.push(word);
239
  state.score += word.length * 10;
240
  state.height += word.length;
241
 
 
 
 
242
  // Add to found words display
243
  const wordBadge = document.createElement('span');
244
- wordBadge.className = 'bg-green-100 text-green-800 text-xs font-medium px-2.5 py-0.5 rounded';
245
  wordBadge.textContent = word;
246
  elements.foundWords.appendChild(wordBadge);
247
 
248
- showFeedback(`+${word.length * 10} points!`, 1500);
 
 
 
 
 
 
 
 
 
 
 
 
249
  } else {
250
- showFeedback("Invalid word!", 1500);
251
  }
252
 
253
- clearSelection();
254
  updateUI();
255
  }
256
 
 
 
 
 
 
 
257
  // Show feedback message
258
  function showFeedback(message, duration) {
259
  elements.feedback.textContent = message;
260
- elements.feedback.className = 'text-lg font-semibold';
261
 
262
  // Set color based on message type
263
- if (message.includes("Invalid") || message.includes("Already") || message.includes("short")) {
264
- elements.feedback.classList.add('text-red-500');
265
  } else if (message.includes("points")) {
266
- elements.feedback.classList.add('text-green-500');
267
  } else {
268
- elements.feedback.classList.add('text-indigo-500');
269
  }
270
-
271
- // Reset animation
272
- elements.feedback.style.animation = 'none';
273
- elements.feedback.offsetHeight; // Trigger reflow
274
- elements.feedback.style.animation = null;
275
-
276
- // Fade out
277
- setTimeout(() => {
278
- elements.feedback.classList.add('opacity-0');
279
- }, duration);
280
  }
281
 
282
  // Update UI elements
@@ -294,9 +819,9 @@
294
 
295
  // Update timer color when low
296
  if (state.timer <= 10) {
297
- elements.timer.classList.add('text-red-600', 'timer-pulse');
298
  } else {
299
- elements.timer.classList.remove('text-red-600', 'timer-pulse');
300
  }
301
 
302
  // Enable/disable submit button
@@ -308,6 +833,17 @@
308
  elements.clearBtn.addEventListener('click', clearSelection);
309
  elements.submitBtn.addEventListener('click', submitWord);
310
 
 
 
 
 
 
 
 
 
 
 
 
311
  // Initialize the game on load
312
  initGame();
313
  </script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Sparkle Tower</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap');
11
+
12
+ body {
13
+ font-family: 'Poppins', sans-serif;
14
+ background: linear-gradient(135deg, #f5f7fa 0%, #ffeef8 100%);
15
+ }
16
+
17
  .letter-tile {
18
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
19
+ background: linear-gradient(135deg, #ffffff 0%, #f9f0ff 100%);
20
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
21
+ border-radius: 12px;
22
+ position: relative;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .letter-tile::before {
27
+ content: '';
28
+ position: absolute;
29
+ top: -50%;
30
+ left: -50%;
31
+ width: 200%;
32
+ height: 200%;
33
+ background: linear-gradient(
34
+ to bottom right,
35
+ rgba(255, 255, 255, 0.3) 0%,
36
+ rgba(255, 255, 255, 0) 60%
37
+ );
38
+ transform: rotate(30deg);
39
+ transition: all 0.3s ease;
40
  }
41
+
42
+ .letter-tile:hover::before {
43
+ left: 100%;
44
+ }
45
+
46
  .letter-tile.selected {
47
+ transform: translateY(-5px) scale(1.05);
48
+ box-shadow: 0 10px 25px rgba(168, 85, 247, 0.3);
49
+ background: linear-gradient(135deg, #f3e8ff 0%, #e9d5ff 100%);
50
+ color: #7e22ce;
51
  }
52
+
53
  .feedback-message {
54
+ animation: fadeInOut 2.5s ease-out forwards;
55
  }
56
+
57
+ @keyframes fadeInOut {
58
+ 0% { opacity: 0; transform: translateY(10px); }
59
+ 20% { opacity: 1; transform: translateY(0); }
60
+ 80% { opacity: 1; transform: translateY(0); }
61
+ 100% { opacity: 0; transform: translateY(-10px); }
62
  }
63
+
64
  .timer-pulse {
65
+ animation: pulse 1.5s infinite;
66
  }
67
+
68
  @keyframes pulse {
69
  0%, 100% { transform: scale(1); }
70
+ 50% { transform: scale(1.1); }
71
+ }
72
+
73
+ #tower-container {
74
+ position: relative;
75
+ height: 250px;
76
+ overflow: hidden;
77
+ background: linear-gradient(to bottom, #faf5ff, #f3e8ff);
78
+ border-radius: 16px;
79
+ margin: 16px;
80
+ box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.05);
81
+ }
82
+
83
+ #tower-canvas {
84
+ position: absolute;
85
+ bottom: 0;
86
+ left: 50%;
87
+ transform-origin: center bottom;
88
+ transform: translateX(-50%);
89
+ transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
90
+ }
91
+
92
+ @keyframes towerSway {
93
+ 0%, 100% { transform: translateX(-50%) rotate(0.5deg); }
94
+ 50% { transform: translateX(-50%) rotate(-0.5deg); }
95
+ }
96
+
97
+ .word-badge {
98
+ position: relative;
99
+ overflow: hidden;
100
+ transition: all 0.3s ease;
101
+ }
102
+
103
+ .word-badge::after {
104
+ content: '';
105
+ position: absolute;
106
+ top: -50%;
107
+ left: -50%;
108
+ width: 200%;
109
+ height: 200%;
110
+ background: linear-gradient(
111
+ to bottom right,
112
+ rgba(255, 255, 255, 0.4) 0%,
113
+ rgba(255, 255, 255, 0) 60%
114
+ );
115
+ transform: rotate(30deg);
116
+ transition: all 0.5s ease;
117
+ }
118
+
119
+ .word-badge:hover::after {
120
+ left: 100%;
121
+ }
122
+
123
+ .sparkle {
124
+ position: absolute;
125
+ width: 4px;
126
+ height: 4px;
127
+ background: white;
128
+ border-radius: 50%;
129
+ pointer-events: none;
130
+ opacity: 0;
131
+ animation: sparkle 1s ease-out forwards;
132
+ }
133
+
134
+ @keyframes sparkle {
135
+ 0% { transform: scale(0); opacity: 0; }
136
+ 50% { transform: scale(1); opacity: 1; }
137
+ 100% { transform: scale(0); opacity: 0; }
138
+ }
139
+
140
+ .floating {
141
+ animation: float 3s ease-in-out infinite;
142
+ }
143
+
144
+ @keyframes float {
145
+ 0%, 100% { transform: translateY(0); }
146
+ 50% { transform: translateY(-10px); }
147
+ }
148
+
149
+ .btn-primary {
150
+ background: linear-gradient(135deg, #a855f7 0%, #8b5cf6 100%);
151
+ box-shadow: 0 4px 15px rgba(168, 85, 247, 0.3);
152
+ transition: all 0.3s ease;
153
+ position: relative;
154
+ overflow: hidden;
155
+ }
156
+
157
+ .btn-primary:hover {
158
+ transform: translateY(-2px);
159
+ box-shadow: 0 8px 20px rgba(168, 85, 247, 0.4);
160
+ }
161
+
162
+ .btn-primary::after {
163
+ content: '';
164
+ position: absolute;
165
+ top: -50%;
166
+ left: -50%;
167
+ width: 200%;
168
+ height: 200%;
169
+ background: linear-gradient(
170
+ to bottom right,
171
+ rgba(255, 255, 255, 0.3) 0%,
172
+ rgba(255, 255, 255, 0) 60%
173
+ );
174
+ transform: rotate(30deg);
175
+ transition: all 0.5s ease;
176
+ }
177
+
178
+ .btn-primary:hover::after {
179
+ left: 100%;
180
+ }
181
+
182
+ .btn-secondary {
183
+ background: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
184
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
185
+ transition: all 0.3s ease;
186
+ position: relative;
187
+ overflow: hidden;
188
+ }
189
+
190
+ .btn-secondary:hover {
191
+ transform: translateY(-2px);
192
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
193
+ }
194
+
195
+ .btn-secondary::after {
196
+ content: '';
197
+ position: absolute;
198
+ top: -50%;
199
+ left: -50%;
200
+ width: 200%;
201
+ height: 200%;
202
+ background: linear-gradient(
203
+ to bottom right,
204
+ rgba(255, 255, 255, 0.4) 0%,
205
+ rgba(255, 255, 255, 0) 60%
206
+ );
207
+ transform: rotate(30deg);
208
+ transition: all 0.5s ease;
209
+ }
210
+
211
+ .btn-secondary:hover::after {
212
+ left: 100%;
213
+ }
214
+
215
+ .crown {
216
+ position: absolute;
217
+ top: -15px;
218
+ left: 50%;
219
+ transform: translateX(-50%);
220
+ font-size: 24px;
221
+ color: #f59e0b;
222
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
223
+ z-index: 10;
224
  }
225
  </style>
226
  </head>
227
+ <body class="min-h-screen flex items-center justify-center p-4">
228
+ <div class="max-w-md w-full bg-white rounded-3xl shadow-2xl overflow-hidden">
229
  <!-- Game Header -->
230
+ <div class="relative bg-gradient-to-r from-purple-500 to-pink-500 p-6 text-white">
231
+ <div class="absolute top-0 left-0 w-full h-full opacity-10">
232
+ <div class="absolute top-10 left-10 w-20 h-20 rounded-full bg-white"></div>
233
+ <div class="absolute top-20 right-20 w-32 h-32 rounded-full bg-white"></div>
234
+ <div class="absolute bottom-10 left-1/4 w-24 h-24 rounded-full bg-white"></div>
235
+ </div>
236
+ <h1 class="text-3xl font-bold text-center relative z-10 floating">Sparkle Tower</h1>
237
+ <p class="text-center text-purple-100 relative z-10">Build your dream tower with words!</p>
238
+ <div class="absolute -bottom-6 left-1/2 transform -translate-x-1/2 w-12 h-12 bg-white rounded-full flex items-center justify-center shadow-lg">
239
+ <div class="w-8 h-8 bg-gradient-to-r from-purple-500 to-pink-500 rounded-full flex items-center justify-center text-white">
240
+ <i class="fas fa-star"></i>
241
+ </div>
242
+ </div>
243
+ </div>
244
+
245
+ <!-- Tower Visualization -->
246
+ <div id="tower-container" class="relative mt-8">
247
+ <div class="absolute inset-0 flex items-center justify-center" id="tower-placeholder">
248
+ <div class="text-center text-purple-300">
249
+ <i class="fas fa-cloud text-5xl mb-2"></i>
250
+ <p class="text-lg">Your tower will appear here!</p>
251
+ </div>
252
+ </div>
253
+ <canvas id="tower-canvas" width="300" height="800"></canvas>
254
  </div>
255
 
256
  <!-- Game Info -->
257
+ <div class="p-6 bg-gradient-to-r from-purple-50 to-pink-50 flex justify-between items-center relative">
258
  <div class="text-center">
259
+ <p class="text-xs text-purple-500 font-semibold">SCORE</p>
260
+ <p id="score" class="text-3xl font-bold text-purple-800">0</p>
261
  </div>
262
  <div class="text-center">
263
+ <p class="text-xs text-purple-500 font-semibold">HEIGHT</p>
264
+ <p id="height" class="text-3xl font-bold text-purple-800">0</p>
265
  </div>
266
+ <div class="text-center relative">
267
+ <p class="text-xs text-purple-500 font-semibold">TIME</p>
268
+ <p id="timer" class="text-3xl font-bold text-pink-600">60</p>
269
+ <div class="absolute -top-2 -right-2 w-6 h-6 bg-pink-500 rounded-full flex items-center justify-center text-white text-xs">
270
+ <i class="fas fa-clock"></i>
271
+ </div>
272
  </div>
273
+ </div>
274
 
275
  <!-- Feedback Message -->
276
+ <div id="feedback" class="h-16 flex items-center justify-center bg-gradient-to-r from-purple-50 to-pink-50">
277
  <p id="feedback-message" class="text-lg font-semibold opacity-0"></p>
278
  </div>
279
 
280
  <!-- Letter Tiles -->
281
+ <div id="letter-area" class="p-6 grid grid-cols-6 gap-3">
282
  <!-- Letter tiles will be generated here -->
283
  </div>
284
 
285
  <!-- Current Word -->
286
+ <div class="px-6 py-4 bg-gradient-to-r from-purple-50 to-pink-50">
287
+ <div class="bg-white rounded-xl p-4 shadow-inner relative">
288
+ <p class="text-xs text-purple-400 mb-1">Current Word:</p>
289
+ <p id="current-word" class="text-3xl font-mono text-center min-h-10 text-purple-800">-</p>
290
+ <div class="absolute top-0 right-0 mt-2 mr-3 text-purple-300">
291
+ <i class="fas fa-pencil-alt"></i>
292
+ </div>
293
  </div>
294
  </div>
295
 
296
  <!-- Controls -->
297
+ <div class="p-6 flex gap-4">
298
+ <button id="submit-btn" class="flex-1 btn-primary text-white font-bold py-4 px-6 rounded-xl transition disabled:opacity-50 disabled:cursor-not-allowed" disabled>
299
  <i class="fas fa-check mr-2"></i> Submit
300
  </button>
301
+ <button id="clear-btn" class="flex-1 btn-secondary text-purple-800 font-bold py-4 px-6 rounded-xl transition">
302
  <i class="fas fa-eraser mr-2"></i> Clear
303
  </button>
304
  </div>
305
 
306
  <!-- Found Words -->
307
+ <div class="p-6 bg-gradient-to-r from-purple-50 to-pink-50 border-t border-purple-100">
308
+ <div class="flex justify-between items-center mb-2">
309
+ <p class="text-xs text-purple-400">Found Words (<span id="found-count">0</span>):</p>
310
+ <div class="text-purple-300">
311
+ <i class="fas fa-trophy"></i>
312
+ </div>
313
+ </div>
314
+ <div id="found-words" class="flex flex-wrap gap-2">
315
  <!-- Found words will appear here -->
316
  </div>
317
  </div>
318
 
319
  <!-- Start Button -->
320
+ <div class="p-6 bg-white">
321
+ <button id="start-btn" class="w-full btn-primary text-white font-bold py-4 px-6 rounded-xl transition">
322
  <i class="fas fa-play mr-2"></i> Start Game
323
  </button>
324
  </div>
 
328
  // Game configuration
329
  const config = {
330
  roundTime: 60,
331
+ availableLetters: ['A', 'E', 'S', 'T', 'R', 'N', 'L', 'O', 'I', 'D'],
332
  validWords: [
333
  "ART", "EAT", "NET", "RAT", "RENT", "STAR", "START", "TAN", "TEA", "TEN",
334
  "NEST", "RATE", "REST", "SAT", "SEA", "SENT", "SET", "TERN", "EARN", "EAST",
335
  "EATS", "NEAT", "RANT", "SEAT", "STAR", "TEAR", "TENS", "ANTS", "ARTS", "ERAS",
336
+ "NATS", "NEAR", "NEST", "RATS", "SANE", "TARE", "TARN", "TARS", "TEAS", "TENS",
337
+ "LOVE", "DREAM", "STAR", "ROSE", "PEARL", "LOTUS", "DIARY", "STORY", "TALES", "SONG",
338
+ "DANCE", "MELODY", "HEART", "SWEET", "CANDY", "SUNNY", "LIGHT", "ANGEL", "FAIRY", "MAGIC"
339
+ ],
340
+ blockColors: [
341
+ '#FF9FF3', '#FECA57', '#FF6B6B', '#48DBFB', '#1DD1A1',
342
+ '#F368E0', '#FF9FF3', '#00D2D3', '#54A0FF', '#5F27CD',
343
+ '#C56CF0', '#FFB8B8', '#FF9F43', '#EE5253', '#0ABDE3',
344
+ '#10AC84', '#2E86DE', '#341F97', '#B33771', '#6D214F'
345
  ]
346
  };
347
 
 
353
  height: 0,
354
  selectedLetters: [],
355
  foundWords: [],
356
+ timerInterval: null,
357
+ towerBlocks: [],
358
+ swayAnimation: null
359
  };
360
 
361
  // DOM elements
 
370
  clearBtn: document.getElementById('clear-btn'),
371
  startBtn: document.getElementById('start-btn'),
372
  foundWords: document.getElementById('found-words'),
373
+ foundCount: document.getElementById('found-count'),
374
+ towerCanvas: document.getElementById('tower-canvas'),
375
+ towerCtx: document.getElementById('tower-canvas').getContext('2d'),
376
+ towerContainer: document.getElementById('tower-container'),
377
+ towerPlaceholder: document.getElementById('tower-placeholder')
378
  };
379
 
380
  // Initialize the game
 
383
  elements.letterArea.innerHTML = '';
384
  config.availableLetters.forEach(letter => {
385
  const tile = document.createElement('div');
386
+ tile.className = 'letter-tile flex items-center justify-center text-3xl font-bold cursor-pointer h-16';
387
  tile.textContent = letter;
388
  tile.dataset.letter = letter;
389
+
390
+ // Add sparkle effect on click
391
+ tile.addEventListener('click', (e) => {
392
+ toggleLetter(letter, tile);
393
+ createSparkle(e);
394
+ });
395
+
396
  elements.letterArea.appendChild(tile);
397
  });
398
 
 
403
  state.height = 0;
404
  state.selectedLetters = [];
405
  state.foundWords = [];
406
+ state.towerBlocks = [];
407
+
408
+ // Clear found words
409
+ elements.foundWords.innerHTML = '';
410
+
411
+ // Stop any existing sway animation
412
+ if (state.swayAnimation) {
413
+ cancelAnimationFrame(state.swayAnimation);
414
+ }
415
+
416
+ // Reset tower canvas
417
+ elements.towerCanvas.style.transform = 'translateX(-50%) scale(1)';
418
+ elements.towerCanvas.style.animation = 'none';
419
+ elements.towerCtx.clearRect(0, 0, elements.towerCanvas.width, elements.towerCanvas.height);
420
+
421
+ // Show placeholder
422
+ elements.towerPlaceholder.style.display = 'flex';
423
 
424
  // Update UI
425
  updateUI();
426
  }
427
 
428
+ // Create sparkle effect
429
+ function createSparkle(event) {
430
+ const sparkle = document.createElement('div');
431
+ sparkle.className = 'sparkle';
432
+
433
+ // Position the sparkle at the click location
434
+ const rect = event.currentTarget.getBoundingClientRect();
435
+ const x = event.clientX - rect.left;
436
+ const y = event.clientY - rect.top;
437
+
438
+ sparkle.style.left = `${x}px`;
439
+ sparkle.style.top = `${y}px`;
440
+
441
+ event.currentTarget.appendChild(sparkle);
442
+
443
+ // Remove after animation
444
+ setTimeout(() => {
445
+ sparkle.remove();
446
+ }, 1000);
447
+ }
448
+
449
+ // Calculate the optimal scale for the tower
450
+ function calculateTowerScale() {
451
+ const containerHeight = elements.towerContainer.clientHeight;
452
+ const towerHeight = state.towerBlocks.length * 25 + 40; // 25px per block + some margin
453
+
454
+ // Calculate scale to fit the tower in the container
455
+ const scale = Math.min(1, containerHeight / towerHeight * 0.85);
456
+
457
+ return scale;
458
+ }
459
+
460
+ // Draw the tower with current scale
461
+ function drawTower() {
462
+ const scale = calculateTowerScale();
463
+
464
+ // Apply the scale transform
465
+ elements.towerCanvas.style.transform = `translateX(-50%) scale(${scale})`;
466
+
467
+ // Clear canvas
468
+ elements.towerCtx.clearRect(0, 0, elements.towerCanvas.width, elements.towerCanvas.height);
469
+
470
+ // Draw sky gradient
471
+ const skyGradient = elements.towerCtx.createLinearGradient(0, 0, 0, elements.towerCanvas.height);
472
+ skyGradient.addColorStop(0, '#f3e8ff');
473
+ skyGradient.addColorStop(1, '#fae8ff');
474
+ elements.towerCtx.fillStyle = skyGradient;
475
+ elements.towerCtx.fillRect(0, 0, elements.towerCanvas.width, elements.towerCanvas.height);
476
+
477
+ // Draw ground
478
+ elements.towerCtx.fillStyle = '#e9d5ff';
479
+ elements.towerCtx.fillRect(0, elements.towerCanvas.height - 15, elements.towerCanvas.width, 15);
480
+
481
+ // Draw grass details
482
+ elements.towerCtx.fillStyle = '#a855f7';
483
+ for (let i = 0; i < 20; i++) {
484
+ const x = Math.random() * elements.towerCanvas.width;
485
+ const height = 5 + Math.random() * 10;
486
+ elements.towerCtx.fillRect(x, elements.towerCanvas.height - 15, 2, -height);
487
+ }
488
+
489
+ // Draw each block in the tower
490
+ state.towerBlocks.forEach((block, index) => {
491
+ const yPos = elements.towerCanvas.height - 25 - (index * 25);
492
+
493
+ // Add slight horizontal offset for "wavy" effect
494
+ const waveOffset = Math.sin(Date.now() / 500 + index * 0.3) * 4;
495
+
496
+ // Block
497
+ elements.towerCtx.fillStyle = block.color;
498
+ elements.towerCtx.beginPath();
499
+ elements.towerCtx.roundRect(block.x + waveOffset, yPos, block.width, 20, [0, 0, 8, 8]);
500
+ elements.towerCtx.fill();
501
+
502
+ // Block pattern
503
+ elements.towerCtx.fillStyle = 'rgba(255, 255, 255, 0.3)';
504
+ for (let i = 0; i < block.width / 15; i++) {
505
+ const x = block.x + waveOffset + 5 + i * 15;
506
+ elements.towerCtx.beginPath();
507
+ elements.towerCtx.arc(x, yPos + 10, 2, 0, Math.PI * 2);
508
+ elements.towerCtx.fill();
509
+ }
510
+
511
+ // Block border
512
+ elements.towerCtx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
513
+ elements.towerCtx.lineWidth = 1;
514
+ elements.towerCtx.beginPath();
515
+ elements.towerCtx.roundRect(block.x + waveOffset, yPos, block.width, 20, [0, 0, 8, 8]);
516
+ elements.towerCtx.stroke();
517
+ });
518
+
519
+ // Draw tower top if there are blocks
520
+ if (state.towerBlocks.length > 0) {
521
+ const topBlock = state.towerBlocks[state.towerBlocks.length - 1];
522
+ const topY = elements.towerCanvas.height - 25 - (state.towerBlocks.length * 25);
523
+ const waveOffset = Math.sin(Date.now() / 500 + state.towerBlocks.length * 0.3) * 4;
524
+
525
+ // Flag
526
+ elements.towerCtx.fillStyle = '#FF9FF3';
527
+ elements.troyCtx.beginPath();
528
+ elements.towerCtx.moveTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 15);
529
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + 20 + waveOffset, topY - 5);
530
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY + 5);
531
+ elements.towerCtx.closePath();
532
+ elements.towerCtx.fill();
533
+
534
+ // Flag details
535
+ elements.towerCtx.fillStyle = '#FF6B6B';
536
+ elements.towerCtx.beginPath();
537
+ elements.towerCtx.arc(topBlock.x + topBlock.width/2 + 10 + waveOffset, topY - 10, 3, 0, Math.PI * 2);
538
+ elements.towerCtx.fill();
539
+
540
+ // Flag pole
541
+ elements.towerCtx.strokeStyle = '#FFFFFF';
542
+ elements.towerCtx.lineWidth = 2;
543
+ elements.towerCtx.beginPath();
544
+ elements.towerCtx.moveTo(topBlock.x + topBlock.width/2 + waveOffset, topY);
545
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 15);
546
+ elements.towerCtx.stroke();
547
+
548
+ // Add crown to the tower if it's tall enough
549
+ if (state.towerBlocks.length > 10) {
550
+ elements.towerCtx.fillStyle = '#FECA57';
551
+ elements.towerCtx.beginPath();
552
+ elements.towerCtx.moveTo(topBlock.x + topBlock.width/2 + waveOffset - 15, topY - 25);
553
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 40);
554
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset + 15, topY - 25);
555
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset + 10, topY - 25);
556
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset, topY - 35);
557
+ elements.towerCtx.lineTo(topBlock.x + topBlock.width/2 + waveOffset - 10, topY - 25);
558
+ elements.towerCtx.closePath();
559
+ elements.towerCtx.fill();
560
+
561
+ elements.towerCtx.strokeStyle = '#FF9F43';
562
+ elements.towerCtx.lineWidth = 1;
563
+ elements.towerCtx.stroke();
564
+ }
565
+ }
566
+
567
+ // Continue the animation loop for the waving effect
568
+ if (state.gameActive) {
569
+ state.swayAnimation = requestAnimationFrame(drawTower);
570
+ }
571
+ }
572
+
573
+ // Add a block to the tower
574
+ function addTowerBlock(wordLength) {
575
+ // Hide placeholder when first block is added
576
+ if (state.towerBlocks.length === 0) {
577
+ elements.towerPlaceholder.style.display = 'none';
578
+ }
579
+
580
+ // Determine block properties
581
+ const color = config.blockColors[Math.floor(Math.random() * config.blockColors.length)];
582
+ const width = 50 + (wordLength * 6); // Bigger blocks for longer words
583
+ const x = (elements.towerCanvas.width - width) / 2; // Center the block
584
+
585
+ // Add slight random offset for visual interest
586
+ const xOffset = Math.random() * 10 - 5;
587
+
588
+ // Add the block to our tower
589
+ state.towerBlocks.push({
590
+ x: x + xOffset,
591
+ width: width,
592
+ color: color,
593
+ wordLength: wordLength
594
+ });
595
+
596
+ // Start the waving animation if not already running
597
+ if (!state.swayAnimation) {
598
+ drawTower();
599
+ }
600
+
601
+ // Add a little growth animation
602
+ animateTowerGrowth();
603
+
604
+ // Add floating hearts occasionally
605
+ if (Math.random() > 0.7) {
606
+ createFloatingHeart();
607
+ }
608
+ }
609
+
610
+ // Create floating heart effect
611
+ function createFloatingHeart() {
612
+ const heart = document.createElement('div');
613
+ heart.className = 'absolute text-pink-400 text-xl';
614
+ heart.innerHTML = '<i class="fas fa-heart"></i>';
615
+
616
+ // Position at random location in tower container
617
+ const containerRect = elements.towerContainer.getBoundingClientRect();
618
+ const x = Math.random() * containerRect.width;
619
+
620
+ heart.style.left = `${x}px`;
621
+ heart.style.bottom = '0';
622
+ heart.style.opacity = '0';
623
+ heart.style.transform = 'translateY(0)';
624
+
625
+ elements.towerContainer.appendChild(heart);
626
+
627
+ // Animate heart floating up
628
+ setTimeout(() => {
629
+ heart.style.transition = 'all 3s ease-out';
630
+ heart.style.opacity = '1';
631
+ heart.style.transform = `translateY(-${containerRect.height}px)`;
632
+
633
+ // Remove after animation
634
+ setTimeout(() => {
635
+ heart.remove();
636
+ }, 3000);
637
+ }, 0);
638
+ }
639
+
640
+ // Animate tower growth
641
+ function animateTowerGrowth() {
642
+ let scale = calculateTowerScale();
643
+ let tempScale = scale * 1.1; // Start slightly larger
644
+
645
+ const animate = () => {
646
+ if (tempScale <= scale) return;
647
+
648
+ tempScale -= 0.005;
649
+ elements.towerCanvas.style.transform = `translateX(-50%) scale(${tempScale})`;
650
+
651
+ requestAnimationFrame(animate);
652
+ };
653
+
654
+ animate();
655
+ }
656
+
657
  // Start a new round
658
  function startRound() {
659
  if (state.timerInterval) clearInterval(state.timerInterval);
 
669
  if (state.timer <= 0) {
670
  endRound();
671
  }
672
+
673
+ // Add timer urgency effect
674
+ if (state.timer <= 10 && !elements.timer.classList.contains('timer-pulse')) {
675
+ elements.timer.classList.add('timer-pulse');
676
+ }
677
  }, 1000);
678
 
679
+ showFeedback("✨ Build your dream tower with words! ✨", 2000);
680
  }
681
 
682
  // End the current round
683
  function endRound() {
684
  state.gameActive = false;
685
  clearInterval(state.timerInterval);
686
+
687
+ // Special message based on performance
688
+ let message;
689
+ if (state.score >= 500) {
690
+ message = `Amazing! You scored ${state.score} points! πŸ‘‘`;
691
+ } else if (state.score >= 300) {
692
+ message = `Great job! You scored ${state.score} points! πŸ’–`;
693
+ } else {
694
+ message = `You scored ${state.score} points! Try again! ✨`;
695
+ }
696
+
697
+ showFeedback(message, 3000);
698
+
699
+ // Stop the sway animation
700
+ if (state.swayAnimation) {
701
+ cancelAnimationFrame(state.swayAnimation);
702
+ state.swayAnimation = null;
703
+ }
704
  }
705
 
706
  // Toggle letter selection
 
712
  if (index === -1) {
713
  // Select the letter
714
  state.selectedLetters.push(letter);
715
+ tile.classList.add('selected');
716
  } else {
717
  // Deselect the letter (remove last occurrence)
718
  const lastIndex = state.selectedLetters.lastIndexOf(letter);
719
  if (lastIndex !== -1) {
720
  state.selectedLetters.splice(lastIndex, 1);
721
  }
722
+ tile.classList.remove('selected');
723
  }
724
 
725
  updateUI();
 
731
 
732
  state.selectedLetters = [];
733
  document.querySelectorAll('.letter-tile').forEach(tile => {
734
+ tile.classList.remove('selected');
735
  });
736
 
737
  updateUI();
 
740
  // Submit current word
741
  function submitWord() {
742
  if (!state.gameActive || state.selectedLetters.length < 3) {
743
+ showFeedback(state.selectedLetters.length < 3 ? "Word too short! (min 3 letters)" : "Game not active", 1500);
744
  return;
745
  }
746
 
747
+ const word = state.selectedLetters.join('').toUpperCase();
748
 
749
  if (state.foundWords.includes(word)) {
750
+ showFeedback("Already found! Try another word", 1500);
751
  } else if (config.validWords.includes(word)) {
752
  // Valid word
753
  state.foundWords.push(word);
754
  state.score += word.length * 10;
755
  state.height += word.length;
756
 
757
+ // Add a block to the tower for this word
758
+ addTowerBlock(word.length);
759
+
760
  // Add to found words display
761
  const wordBadge = document.createElement('span');
762
+ wordBadge.className = 'word-badge bg-gradient-to-r from-purple-100 to-pink-100 text-purple-800 text-sm font-medium px-3 py-1 rounded-full';
763
  wordBadge.textContent = word;
764
  elements.foundWords.appendChild(wordBadge);
765
 
766
+ // Add sparkle effect to the badge
767
+ wordBadge.addEventListener('mouseenter', (e) => {
768
+ for (let i = 0; i < 3; i++) {
769
+ setTimeout(() => {
770
+ createSparkle(e);
771
+ }, i * 200);
772
+ }
773
+ });
774
+
775
+ showFeedback(`+${word.length * 10} points! ${getRandomEmoji()}`, 1500);
776
+
777
+ // Clear the selection after successful submission
778
+ clearSelection();
779
  } else {
780
+ showFeedback("Not in our dictionary", 1500);
781
  }
782
 
 
783
  updateUI();
784
  }
785
 
786
+ // Get random celebratory emoji
787
+ function getRandomEmoji() {
788
+ const emojis = ['✨', '🌸', 'πŸ’–', 'πŸŽ€', '🌈', 'πŸ’«', '🌟', 'πŸ’Ž'];
789
+ return emojis[Math.floor(Math.random() * emojis.length)];
790
+ }
791
+
792
  // Show feedback message
793
  function showFeedback(message, duration) {
794
  elements.feedback.textContent = message;
795
+ elements.feedback.className = 'text-lg font-semibold feedback-message';
796
 
797
  // Set color based on message type
798
+ if (message.includes("Not") || message.includes("Already") || message.includes("short")) {
799
+ elements.feedback.classList.add('text-pink-500');
800
  } else if (message.includes("points")) {
801
+ elements.feedback.classList.add('text-purple-600');
802
  } else {
803
+ elements.feedback.classList.add('text-pink-400');
804
  }
 
 
 
 
 
 
 
 
 
 
805
  }
806
 
807
  // Update UI elements
 
819
 
820
  // Update timer color when low
821
  if (state.timer <= 10) {
822
+ elements.timer.classList.add('text-pink-600');
823
  } else {
824
+ elements.timer.classList.remove('text-pink-600');
825
  }
826
 
827
  // Enable/disable submit button
 
833
  elements.clearBtn.addEventListener('click', clearSelection);
834
  elements.submitBtn.addEventListener('click', submitWord);
835
 
836
+ // Add sparkle effect to buttons on hover
837
+ [elements.startBtn, elements.submitBtn, elements.clearBtn].forEach(btn => {
838
+ btn.addEventListener('mouseenter', (e) => {
839
+ for (let i = 0; i < 3; i++) {
840
+ setTimeout(() => {
841
+ createSparkle(e);
842
+ }, i * 200);
843
+ }
844
+ });
845
+ });
846
+
847
  // Initialize the game on load
848
  initGame();
849
  </script>
prompts.txt CHANGED
@@ -1 +1,6 @@
1
- Okay, here is an implementation-grade detailed plan for an interactive demonstrator focusing *only* on the **core game mechanics** of **Tower Text Twist**. The goal is to create a minimal, functional prototype suitable for gathering user feedback on the core loop's feel and clarity, using a limited codebase (e.g., achievable by 1 developer in a short timeframe). **I. Demonstrator Overview & Goal** * **Purpose:** Validate the core word-finding loop: presenting letters, user input, validation, scoring, height point generation, and timer pressure. * **Target Experience:** Allow a user to play one or more timed rounds, understand how to form words, see immediate feedback on validity, and observe how score and height points accumulate. * **Scope Limitations (Crucial for Limited Codebase):** * **NO Meta Game:** No visual tower, no milestones, no themes, no long-term progression saved. * **NO Idle Element:** No passive point generation. * **NO Complex Letter Generation:** Use a fixed, predefined set of letters for all rounds of the demo (e.g., 'A', 'E', 'S', 'T', 'R', 'N'). * **NO Advanced UI/Polish:** Basic, functional UI only. Minimal animations or effects. * **NO Sound (Optional):** Sound effects are secondary; can be added if time permits but are not core to the mechanic validation. * **NO Monetization Hooks:** No ads, no IAP prompts. * **Simplified Dictionary:** Use a relatively small, curated word list focusing on common English words of 3-6 letters derivable from the chosen letter set. * **Platform:** PC Build or WebGL build (preferred for easy distribution for feedback). **II. Technology Stack** * **Engine:** Unity (Recommended for ease of UI, component structure, and potential future expansion). * **Language:** C# * **IDE:** Visual Studio / VS Code / Rider **III. Key Components & Scene Structure (Unity)** 1. **Scene:** `CoreMechanicsDemo` 2. **Main Camera:** Standard setup. 3. **Canvas (UI Root):** * **Panel\_LetterArea:** Holds the letter tiles. * **LetterTile (Prefab):** Represents one available letter. Contains: * `Image` (background) * `Text` (displaying the letter) * `Button` (for interaction) * `LetterTile.cs` (Script) * **Panel\_InputDisplay:** Shows the currently selected word. * `Text_CurrentWord`: Displays the sequence of selected letters. * **Button\_SubmitWord:** Button to submit the selected word. * **Panel\_GameInfo:** Displays scores and timer. * `Text_Timer`: Shows remaining time. * `Text_Score`: Shows current round score. * `Text_HeightPoints`: Shows current round height points earned. * `Text_FoundWordsCount`: (Optional) Shows how many words found this round. * **Panel\_Feedback:** Displays messages to the user. * `Text_FeedbackMessage`: Shows "Valid Word!", "Invalid Word", "Already Found", "Time's Up!". Fades out after a short duration. * **Panel\_Control:** Holds start/reset buttons. * `Button_StartRound`: Initiates a new round. * `Text_Instructions`: (Optional) Simple text: "Find words using the letters above. Click letters to select, click Submit." 4. **GameManager (Empty GameObject):** * `GameManager.cs` (Script - Singleton potentially useful) * `WordValidator.cs` (Script - Can be separate or part of GameManager) **IV. Core Scripting Details** **1. `GameManager.cs`** ```csharp using UnityEngine; using UnityEngine.UI; using System.Collections; using System.Collections.Generic; // For Lists public class GameManager : MonoBehaviour { // --- Inspector References --- [Header("UI Elements")] [SerializeField] private Text timerText; [SerializeField] private Text scoreText; [SerializeField] private Text heightPointsText; [SerializeField] private Text currentWordText; // Reference to InputDisplay's text [SerializeField] private Text feedbackText; [SerializeField] private Button startRoundButton; [SerializeField] private Button submitWordButton; [SerializeField] private GameObject letterTilePrefab; [SerializeField] private Transform letterAreaPanel; // Parent for letter tiles [Header("Game Settings")] [SerializeField] private float roundTimeSeconds = 60f; [SerializeField] private char[] availableLetters = {'A', 'E', 'S', 'T', 'R', 'N'}; // Fixed for demo // --- Game State --- private enum GameState { Idle, Playing, RoundOver } private GameState currentState = GameState.Idle; private float currentTimer; private int currentScore; private int currentHeightPoints; private List<string> foundWordsThisRound = new List<string>(); private List<LetterTile> currentLetterTiles = new List<LetterTile>(); private string selectedWord = ""; // --- Dependencies --- private WordValidator wordValidator; void Start() { wordValidator = GetComponent<WordValidator>(); // Assumes WordValidator is on the same GameObject if (wordValidator == null) { Debug.LogError("WordValidator component not found on GameManager!"); return; } wordValidator.LoadDictionary(); // Load words on start // Initial UI State startRoundButton.onClick.AddListener(StartRound); submitWordButton.onClick.AddListener(TrySubmitWord); submitWordButton.interactable = false; feedbackText.text = ""; UpdateUI(); } void Update() { if (currentState == GameState.Playing) { currentTimer -= Time.deltaTime; if (currentTimer <= 0f) { EndRound(); } UpdateUI(); // Update timer display constantly } } void StartRound() { currentState = GameState.Playing; currentTimer = roundTimeSeconds; currentScore = 0; currentHeightPoints = 0; foundWordsThisRound.Clear(); selectedWord = ""; // Clear previous tiles and create new ones foreach (Transform child in letterAreaPanel) { Destroy(child.gameObject); } currentLetterTiles.Clear(); foreach (char letter in availableLetters) { GameObject tileGO = Instantiate(letterTilePrefab, letterAreaPanel); LetterTile tile = tileGO.GetComponent<LetterTile>(); tile.Setup(letter, this); currentLetterTiles.Add(tile); } startRoundButton.interactable = false; submitWordButton.interactable = true; // Enable submit when playing ShowFeedback("", 0f); // Clear feedback UpdateUI(); } void EndRound() { currentState = GameState.RoundOver; currentTimer = 0f; startRoundButton.interactable = true; submitWordButton.interactable = false; DeselectAllTiles(); ShowFeedback("Time's Up!", 3.0f); // Show feedback longer UpdateUI(); // Final UI update } public void LetterTileClicked(LetterTile tile) { if (currentState != GameState.Playing) return; if (tile.IsSelected) { // Deselect - Remove last occurrence of the letter int index = selectedWord.LastIndexOf(tile.Letter); if(index != -1) { // Should always find if selected, but check anyway selectedWord = selectedWord.Remove(index, 1); } tile.SetSelected(false); } else { // Select - Add letter selectedWord += tile.Letter; tile.SetSelected(true); } UpdateUI(); } private void DeselectAllTiles() { selectedWord = ""; foreach(LetterTile tile in currentLetterTiles) { tile.SetSelected(false); } UpdateUI(); } void TrySubmitWord() { if (currentState != GameState.Playing || selectedWord.Length < 3) // Min word length 3 { ShowFeedback(selectedWord.Length < 3 ? "Word too short!" : "Invalid action", 1.5f); DeselectAllTiles(); return; // Don't submit if too short or not playing } string wordToCheck = selectedWord; // Keep a copy before clearing if (foundWordsThisRound.Contains(wordToCheck)) { ShowFeedback("Already Found!", 1.5f); } else if (wordValidator.IsWordValid(wordToCheck, availableLetters)) { foundWordsThisRound.Add(wordToCheck); currentScore += CalculateScore(wordToCheck); currentHeightPoints += wordToCheck.Length; // 1 point per letter ShowFeedback("Valid Word!", 1.5f); } else { ShowFeedback("Invalid Word!", 1.5f); } DeselectAllTiles(); // Clear selection after every submission attempt UpdateUI(); // Update score/height points display } int CalculateScore(string word) { // Simple scoring: Length * 10 (e.g.) return word.Length * 10; } void UpdateUI() { timerText.text = $"Time: {Mathf.CeilToInt(currentTimer)}"; scoreText.text = $"Score: {currentScore}"; heightPointsText.text = $"Height: {currentHeightPoints}"; currentWordText.text = selectedWord; // Display selected letters } void ShowFeedback(string message, float duration) { StopCoroutine("FadeFeedback"); // Stop previous fade if any feedbackText.text = message; if (duration > 0) { StartCoroutine(FadeFeedback(duration)); } } IEnumerator FadeFeedback(float duration) { yield return new WaitForSeconds(duration); feedbackText.text = ""; } } ``` **2. `LetterTile.cs` (Attach to LetterTile Prefab)** ```csharp using UnityEngine; using UnityEngine.UI; public class LetterTile : MonoBehaviour { [SerializeField] private Text letterText; [SerializeField] private Image backgroundImage; // To change color on select [SerializeField] private Color baseColor = Color.white; [SerializeField] private Color selectedColor = Color.cyan; private Button button; private GameManager gameManager; private char myLetter; private bool isSelected = false; public char Letter => myLetter; public bool IsSelected => isSelected; void Awake() { button = GetComponent<Button>(); button.onClick.AddListener(OnTileClicked); } public void Setup(char letter, GameManager manager) { myLetter = letter; letterText.text = myLetter.ToString(); gameManager = manager; SetSelected(false); // Ensure default state } void OnTileClicked() { if (gameManager != null) { gameManager.LetterTileClicked(this); } } public void SetSelected(bool selected) { isSelected = selected; backgroundImage.color = isSelected ? selectedColor : baseColor; // Optional: Add visual feedback like slight scale change } } ``` **3. `WordValidator.cs` (Attach to GameManager GameObject)** ```csharp using UnityEngine; using System.Collections.Generic; // For HashSet using System.Linq; // For Linq operations like Count() public class WordValidator : MonoBehaviour { [SerializeField] private TextAsset dictionaryFile; // Assign your simple TXT file in Inspector private HashSet<string> validWords = new HashSet<string>(); public void LoadDictionary() { if (dictionaryFile == null) { Debug.LogError("Dictionary file not assigned!"); return; } string[] words = dictionaryFile.text.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.RemoveEmptyEntries); foreach (string word in words) { validWords.Add(word.ToUpper()); // Store in uppercase for case-insensitive comparison } Debug.Log($"Loaded {validWords.Count} words into dictionary."); } public bool IsWordValid(string word, char[] availableLetters) { string upperWord = word.ToUpper(); // Compare in uppercase // 1. Check if it's in the dictionary if (!validWords.Contains(upperWord)) { // Debug.Log($"Word '{upperWord}' not in dictionary."); return false; } // 2. Check if the word can be formed from available letters (simple check for demo) // This check ensures you don't use a letter more times than it's available var availableLetterCounts = availableLetters.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count()); var wordLetterCounts = upperWord.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count()); foreach (var kvp in wordLetterCounts) { char letter = kvp.Key; int countNeeded = kvp.Value; if (!availableLetterCounts.ContainsKey(letter) || availableLetterCounts[letter] < countNeeded) { // Debug.Log($"Word '{upperWord}' needs {countNeeded} of '{letter}', only { (availableLetterCounts.ContainsKey(letter) ? availableLetterCounts[letter] : 0) } available."); return false; // Letter not available or not enough instances } } // Debug.Log($"Word '{upperWord}' is valid."); return true; } } ``` **V. Data & Assets** * **Dictionary File (`dictionary.txt`):** Create a simple text file. Place it in a `Resources` folder within your Unity `Assets`. Populate it with valid English words (UPPERCASE recommended for simpler C# logic, 3-6 letters long) that can be formed *only* using your chosen `availableLetters` (e.g., "ART", "EAT", "NET", "RAT", "RENT", "STAR", "START", "TAN", "TEA", "TEN", "NEST", "RATE", "REST", "SAT", "SEA", "SENT", "SET", "TAN", "TEA", "TEN", "TERN", "EARN", "EAST", "EATS", "RENT", "REST", "RATE"). * **LetterTile Prefab:** Create a prefab from a UI Button, customize its Image and Text components as needed. Attach `LetterTile.cs`. **VI. Scene Setup Steps** 1. Create the UI elements as described in Section III. Anchor them appropriately. 2. Assign UI Text and Button components to the corresponding `[SerializeField]` fields in the `GameManager` script in the Inspector. 3. Assign the `dictionary.txt` file to the `dictionaryFile` field on the `WordValidator` component (which is on the GameManager object). 4. Assign the LetterTile prefab to the `letterTilePrefab` field in `GameManager`. 5. Assign the `Panel_LetterArea` Transform to the `letterAreaPanel` field in `GameManager`. 6. Ensure `WordValidator.cs` is attached to the same GameObject as `GameManager.cs`. **VII. User Feedback Goals for Demonstrator** * **Clarity:** Is it immediately clear how to select letters and form words? Is the Submit action obvious? * **Feedback:** Is the feedback for valid/invalid/found words clear and timely? * **Timer:** Does the timer create a sense of urgency? Is the duration appropriate for finding a few words? * **Scoring/Height:** Is it clear how score and height points are awarded? Does seeing the height points increment feel rewarding, even without the visual tower? * **Input Feel:** Does tapping the letters feel responsive? Is selecting/deselecting intuitive? * **Frustration Points:** Was anything confusing or annoying during the core loop? (e.g., difficulty deselecting, unclear validation rules). This detailed plan provides a functional core loop demonstrator. It intentionally omits many features of the full game to keep the codebase limited and focused on validating the fundamental player interaction before investing in the more complex meta-game systems.
 
 
 
 
 
 
1
+ Okay, here is an implementation-grade detailed plan for an interactive demonstrator focusing *only* on the **core game mechanics** of **Tower Text Twist**. The goal is to create a minimal, functional prototype suitable for gathering user feedback on the core loop's feel and clarity, using a limited codebase (e.g., achievable by 1 developer in a short timeframe). **I. Demonstrator Overview & Goal** * **Purpose:** Validate the core word-finding loop: presenting letters, user input, validation, scoring, height point generation, and timer pressure. * **Target Experience:** Allow a user to play one or more timed rounds, understand how to form words, see immediate feedback on validity, and observe how score and height points accumulate. * **Scope Limitations (Crucial for Limited Codebase):** * **NO Meta Game:** No visual tower, no milestones, no themes, no long-term progression saved. * **NO Idle Element:** No passive point generation. * **NO Complex Letter Generation:** Use a fixed, predefined set of letters for all rounds of the demo (e.g., 'A', 'E', 'S', 'T', 'R', 'N'). * **NO Advanced UI/Polish:** Basic, functional UI only. Minimal animations or effects. * **NO Sound (Optional):** Sound effects are secondary; can be added if time permits but are not core to the mechanic validation. * **NO Monetization Hooks:** No ads, no IAP prompts. * **Simplified Dictionary:** Use a relatively small, curated word list focusing on common English words of 3-6 letters derivable from the chosen letter set. * **Platform:** PC Build or WebGL build (preferred for easy distribution for feedback). **II. Technology Stack** * **Engine:** Unity (Recommended for ease of UI, component structure, and potential future expansion). * **Language:** C# * **IDE:** Visual Studio / VS Code / Rider **III. Key Components & Scene Structure (Unity)** 1. **Scene:** `CoreMechanicsDemo` 2. **Main Camera:** Standard setup. 3. **Canvas (UI Root):** * **Panel\_LetterArea:** Holds the letter tiles. * **LetterTile (Prefab):** Represents one available letter. Contains: * `Image` (background) * `Text` (displaying the letter) * `Button` (for interaction) * `LetterTile.cs` (Script) * **Panel\_InputDisplay:** Shows the currently selected word. * `Text_CurrentWord`: Displays the sequence of selected letters. * **Button\_SubmitWord:** Button to submit the selected word. * **Panel\_GameInfo:** Displays scores and timer. * `Text_Timer`: Shows remaining time. * `Text_Score`: Shows current round score. * `Text_HeightPoints`: Shows current round height points earned. * `Text_FoundWordsCount`: (Optional) Shows how many words found this round. * **Panel\_Feedback:** Displays messages to the user. * `Text_FeedbackMessage`: Shows "Valid Word!", "Invalid Word", "Already Found", "Time's Up!". Fades out after a short duration. * **Panel\_Control:** Holds start/reset buttons. * `Button_StartRound`: Initiates a new round. * `Text_Instructions`: (Optional) Simple text: "Find words using the letters above. Click letters to select, click Submit." 4. **GameManager (Empty GameObject):** * `GameManager.cs` (Script - Singleton potentially useful) * `WordValidator.cs` (Script - Can be separate or part of GameManager) **IV. Core Scripting Details** **1. `GameManager.cs`** ```csharp using UnityEngine; using UnityEngine.UI; using System.Collections; using System.Collections.Generic; // For Lists public class GameManager : MonoBehaviour { // --- Inspector References --- [Header("UI Elements")] [SerializeField] private Text timerText; [SerializeField] private Text scoreText; [SerializeField] private Text heightPointsText; [SerializeField] private Text currentWordText; // Reference to InputDisplay's text [SerializeField] private Text feedbackText; [SerializeField] private Button startRoundButton; [SerializeField] private Button submitWordButton; [SerializeField] private GameObject letterTilePrefab; [SerializeField] private Transform letterAreaPanel; // Parent for letter tiles [Header("Game Settings")] [SerializeField] private float roundTimeSeconds = 60f; [SerializeField] private char[] availableLetters = {'A', 'E', 'S', 'T', 'R', 'N'}; // Fixed for demo // --- Game State --- private enum GameState { Idle, Playing, RoundOver } private GameState currentState = GameState.Idle; private float currentTimer; private int currentScore; private int currentHeightPoints; private List<string> foundWordsThisRound = new List<string>(); private List<LetterTile> currentLetterTiles = new List<LetterTile>(); private string selectedWord = ""; // --- Dependencies --- private WordValidator wordValidator; void Start() { wordValidator = GetComponent<WordValidator>(); // Assumes WordValidator is on the same GameObject if (wordValidator == null) { Debug.LogError("WordValidator component not found on GameManager!"); return; } wordValidator.LoadDictionary(); // Load words on start // Initial UI State startRoundButton.onClick.AddListener(StartRound); submitWordButton.onClick.AddListener(TrySubmitWord); submitWordButton.interactable = false; feedbackText.text = ""; UpdateUI(); } void Update() { if (currentState == GameState.Playing) { currentTimer -= Time.deltaTime; if (currentTimer <= 0f) { EndRound(); } UpdateUI(); // Update timer display constantly } } void StartRound() { currentState = GameState.Playing; currentTimer = roundTimeSeconds; currentScore = 0; currentHeightPoints = 0; foundWordsThisRound.Clear(); selectedWord = ""; // Clear previous tiles and create new ones foreach (Transform child in letterAreaPanel) { Destroy(child.gameObject); } currentLetterTiles.Clear(); foreach (char letter in availableLetters) { GameObject tileGO = Instantiate(letterTilePrefab, letterAreaPanel); LetterTile tile = tileGO.GetComponent<LetterTile>(); tile.Setup(letter, this); currentLetterTiles.Add(tile); } startRoundButton.interactable = false; submitWordButton.interactable = true; // Enable submit when playing ShowFeedback("", 0f); // Clear feedback UpdateUI(); } void EndRound() { currentState = GameState.RoundOver; currentTimer = 0f; startRoundButton.interactable = true; submitWordButton.interactable = false; DeselectAllTiles(); ShowFeedback("Time's Up!", 3.0f); // Show feedback longer UpdateUI(); // Final UI update } public void LetterTileClicked(LetterTile tile) { if (currentState != GameState.Playing) return; if (tile.IsSelected) { // Deselect - Remove last occurrence of the letter int index = selectedWord.LastIndexOf(tile.Letter); if(index != -1) { // Should always find if selected, but check anyway selectedWord = selectedWord.Remove(index, 1); } tile.SetSelected(false); } else { // Select - Add letter selectedWord += tile.Letter; tile.SetSelected(true); } UpdateUI(); } private void DeselectAllTiles() { selectedWord = ""; foreach(LetterTile tile in currentLetterTiles) { tile.SetSelected(false); } UpdateUI(); } void TrySubmitWord() { if (currentState != GameState.Playing || selectedWord.Length < 3) // Min word length 3 { ShowFeedback(selectedWord.Length < 3 ? "Word too short!" : "Invalid action", 1.5f); DeselectAllTiles(); return; // Don't submit if too short or not playing } string wordToCheck = selectedWord; // Keep a copy before clearing if (foundWordsThisRound.Contains(wordToCheck)) { ShowFeedback("Already Found!", 1.5f); } else if (wordValidator.IsWordValid(wordToCheck, availableLetters)) { foundWordsThisRound.Add(wordToCheck); currentScore += CalculateScore(wordToCheck); currentHeightPoints += wordToCheck.Length; // 1 point per letter ShowFeedback("Valid Word!", 1.5f); } else { ShowFeedback("Invalid Word!", 1.5f); } DeselectAllTiles(); // Clear selection after every submission attempt UpdateUI(); // Update score/height points display } int CalculateScore(string word) { // Simple scoring: Length * 10 (e.g.) return word.Length * 10; } void UpdateUI() { timerText.text = $"Time: {Mathf.CeilToInt(currentTimer)}"; scoreText.text = $"Score: {currentScore}"; heightPointsText.text = $"Height: {currentHeightPoints}"; currentWordText.text = selectedWord; // Display selected letters } void ShowFeedback(string message, float duration) { StopCoroutine("FadeFeedback"); // Stop previous fade if any feedbackText.text = message; if (duration > 0) { StartCoroutine(FadeFeedback(duration)); } } IEnumerator FadeFeedback(float duration) { yield return new WaitForSeconds(duration); feedbackText.text = ""; } } ``` **2. `LetterTile.cs` (Attach to LetterTile Prefab)** ```csharp using UnityEngine; using UnityEngine.UI; public class LetterTile : MonoBehaviour { [SerializeField] private Text letterText; [SerializeField] private Image backgroundImage; // To change color on select [SerializeField] private Color baseColor = Color.white; [SerializeField] private Color selectedColor = Color.cyan; private Button button; private GameManager gameManager; private char myLetter; private bool isSelected = false; public char Letter => myLetter; public bool IsSelected => isSelected; void Awake() { button = GetComponent<Button>(); button.onClick.AddListener(OnTileClicked); } public void Setup(char letter, GameManager manager) { myLetter = letter; letterText.text = myLetter.ToString(); gameManager = manager; SetSelected(false); // Ensure default state } void OnTileClicked() { if (gameManager != null) { gameManager.LetterTileClicked(this); } } public void SetSelected(bool selected) { isSelected = selected; backgroundImage.color = isSelected ? selectedColor : baseColor; // Optional: Add visual feedback like slight scale change } } ``` **3. `WordValidator.cs` (Attach to GameManager GameObject)** ```csharp using UnityEngine; using System.Collections.Generic; // For HashSet using System.Linq; // For Linq operations like Count() public class WordValidator : MonoBehaviour { [SerializeField] private TextAsset dictionaryFile; // Assign your simple TXT file in Inspector private HashSet<string> validWords = new HashSet<string>(); public void LoadDictionary() { if (dictionaryFile == null) { Debug.LogError("Dictionary file not assigned!"); return; } string[] words = dictionaryFile.text.Split(new[] { "\r\n", "\n" }, System.StringSplitOptions.RemoveEmptyEntries); foreach (string word in words) { validWords.Add(word.ToUpper()); // Store in uppercase for case-insensitive comparison } Debug.Log($"Loaded {validWords.Count} words into dictionary."); } public bool IsWordValid(string word, char[] availableLetters) { string upperWord = word.ToUpper(); // Compare in uppercase // 1. Check if it's in the dictionary if (!validWords.Contains(upperWord)) { // Debug.Log($"Word '{upperWord}' not in dictionary."); return false; } // 2. Check if the word can be formed from available letters (simple check for demo) // This check ensures you don't use a letter more times than it's available var availableLetterCounts = availableLetters.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count()); var wordLetterCounts = upperWord.GroupBy(c => c).ToDictionary(g => g.Key, g => g.Count()); foreach (var kvp in wordLetterCounts) { char letter = kvp.Key; int countNeeded = kvp.Value; if (!availableLetterCounts.ContainsKey(letter) || availableLetterCounts[letter] < countNeeded) { // Debug.Log($"Word '{upperWord}' needs {countNeeded} of '{letter}', only { (availableLetterCounts.ContainsKey(letter) ? availableLetterCounts[letter] : 0) } available."); return false; // Letter not available or not enough instances } } // Debug.Log($"Word '{upperWord}' is valid."); return true; } } ``` **V. Data & Assets** * **Dictionary File (`dictionary.txt`):** Create a simple text file. Place it in a `Resources` folder within your Unity `Assets`. Populate it with valid English words (UPPERCASE recommended for simpler C# logic, 3-6 letters long) that can be formed *only* using your chosen `availableLetters` (e.g., "ART", "EAT", "NET", "RAT", "RENT", "STAR", "START", "TAN", "TEA", "TEN", "NEST", "RATE", "REST", "SAT", "SEA", "SENT", "SET", "TAN", "TEA", "TEN", "TERN", "EARN", "EAST", "EATS", "RENT", "REST", "RATE"). * **LetterTile Prefab:** Create a prefab from a UI Button, customize its Image and Text components as needed. Attach `LetterTile.cs`. **VI. Scene Setup Steps** 1. Create the UI elements as described in Section III. Anchor them appropriately. 2. Assign UI Text and Button components to the corresponding `[SerializeField]` fields in the `GameManager` script in the Inspector. 3. Assign the `dictionary.txt` file to the `dictionaryFile` field on the `WordValidator` component (which is on the GameManager object). 4. Assign the LetterTile prefab to the `letterTilePrefab` field in `GameManager`. 5. Assign the `Panel_LetterArea` Transform to the `letterAreaPanel` field in `GameManager`. 6. Ensure `WordValidator.cs` is attached to the same GameObject as `GameManager.cs`. **VII. User Feedback Goals for Demonstrator** * **Clarity:** Is it immediately clear how to select letters and form words? Is the Submit action obvious? * **Feedback:** Is the feedback for valid/invalid/found words clear and timely? * **Timer:** Does the timer create a sense of urgency? Is the duration appropriate for finding a few words? * **Scoring/Height:** Is it clear how score and height points are awarded? Does seeing the height points increment feel rewarding, even without the visual tower? * **Input Feel:** Does tapping the letters feel responsive? Is selecting/deselecting intuitive? * **Frustration Points:** Was anything confusing or annoying during the core loop? (e.g., difficulty deselecting, unclear validation rules). This detailed plan provides a functional core loop demonstrator. It intentionally omits many features of the full game to keep the codebase limited and focused on validating the fundamental player interaction before investing in the more complex meta-game systems.
2
+ usign procedural graphics add a visual tower from colorful blocks.
3
+ Make the tower view adaptivelly "zoom out" or "zoom to fit" to maintain the entire view of the tower regardless of the height (which increases over time) and make it slightly "wave in the wind" to increase fun...
4
+ Act as wouldclass UI casual game designer tha improve the look and feel to AAA production quality appealing to women.
5
+ Make the selected letters clear after submitted word is accepted.
6
+ Make letters clear after each submit button press.