Spaces:
Running
Running
Add 2 files
Browse files- index.html +608 -72
- 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
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
}
|
|
|
|
|
|
|
|
|
|
|
13 |
.letter-tile.selected {
|
14 |
-
transform: translateY(-5px);
|
15 |
-
box-shadow: 0 10px
|
|
|
|
|
16 |
}
|
|
|
17 |
.feedback-message {
|
18 |
-
animation:
|
19 |
}
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
22 |
}
|
|
|
23 |
.timer-pulse {
|
24 |
-
animation: pulse
|
25 |
}
|
|
|
26 |
@keyframes pulse {
|
27 |
0%, 100% { transform: scale(1); }
|
28 |
-
50% { transform: scale(1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
29 |
}
|
30 |
</style>
|
31 |
</head>
|
32 |
-
<body class="
|
33 |
-
<div class="max-w-md w-full bg-white rounded-
|
34 |
<!-- Game Header -->
|
35 |
-
<div class="bg-
|
36 |
-
<
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
</div>
|
39 |
|
40 |
<!-- Game Info -->
|
41 |
-
<div class="p-
|
42 |
<div class="text-center">
|
43 |
-
<p class="text-xs text-
|
44 |
-
<p id="score" class="text-
|
45 |
</div>
|
46 |
<div class="text-center">
|
47 |
-
<p class="text-xs text-
|
48 |
-
<p id="height" class="text-
|
49 |
</div>
|
50 |
-
<div class="text-center">
|
51 |
-
<p class="text-xs text-
|
52 |
-
<p id="timer" class="text-
|
|
|
|
|
|
|
53 |
</div>
|
54 |
-
|
55 |
|
56 |
<!-- Feedback Message -->
|
57 |
-
<div id="feedback" class="h-
|
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-
|
63 |
<!-- Letter tiles will be generated here -->
|
64 |
</div>
|
65 |
|
66 |
<!-- Current Word -->
|
67 |
-
<div class="px-
|
68 |
-
<div class="bg-white rounded-
|
69 |
-
<p class="text-xs text-
|
70 |
-
<p id="current-word" class="text-
|
|
|
|
|
|
|
71 |
</div>
|
72 |
</div>
|
73 |
|
74 |
<!-- Controls -->
|
75 |
-
<div class="p-
|
76 |
-
<button id="submit-btn" class="flex-1
|
77 |
<i class="fas fa-check mr-2"></i> Submit
|
78 |
</button>
|
79 |
-
<button id="clear-btn" class="flex-1
|
80 |
<i class="fas fa-eraser mr-2"></i> Clear
|
81 |
</button>
|
82 |
</div>
|
83 |
|
84 |
<!-- Found Words -->
|
85 |
-
<div class="p-
|
86 |
-
<
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
88 |
<!-- Found words will appear here -->
|
89 |
</div>
|
90 |
</div>
|
91 |
|
92 |
<!-- Start Button -->
|
93 |
-
<div class="p-
|
94 |
-
<button id="start-btn" class="w-full
|
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
|
146 |
tile.textContent = letter;
|
147 |
tile.dataset.letter = letter;
|
148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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("
|
182 |
}
|
183 |
|
184 |
// End the current round
|
185 |
function endRound() {
|
186 |
state.gameActive = false;
|
187 |
clearInterval(state.timerInterval);
|
188 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'
|
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'
|
208 |
}
|
209 |
|
210 |
updateUI();
|
@@ -216,7 +731,7 @@
|
|
216 |
|
217 |
state.selectedLetters = [];
|
218 |
document.querySelectorAll('.letter-tile').forEach(tile => {
|
219 |
-
tile.classList.remove('selected'
|
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-
|
245 |
wordBadge.textContent = word;
|
246 |
elements.foundWords.appendChild(wordBadge);
|
247 |
|
248 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
249 |
} else {
|
250 |
-
showFeedback("
|
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("
|
264 |
-
elements.feedback.classList.add('text-
|
265 |
} else if (message.includes("points")) {
|
266 |
-
elements.feedback.classList.add('text-
|
267 |
} else {
|
268 |
-
elements.feedback.classList.add('text-
|
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-
|
298 |
} else {
|
299 |
-
elements.timer.classList.remove('text-
|
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.
|