mrwhy06 commited on
Commit
70c07a8
·
verified ·
1 Parent(s): 8b19eda

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +617 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Test V2
3
- emoji: 👀
4
- colorFrom: pink
5
- colorTo: yellow
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: test-v2
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: blue
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,617 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Ball Sort Puzzle</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ font-family: 'Arial', sans-serif;
13
+ }
14
+ body {
15
+ background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
16
+ height: 100vh;
17
+ display: flex;
18
+ flex-direction: column;
19
+ align-items: center;
20
+ justify-content: center;
21
+ color: white;
22
+ overflow: hidden;
23
+ }
24
+ .game-container {
25
+ display: flex;
26
+ flex-direction: column;
27
+ align-items: center;
28
+ width: 100%;
29
+ max-width: 800px;
30
+ padding: 20px;
31
+ position: relative;
32
+ }
33
+ h1 {
34
+ margin-bottom: 20px;
35
+ font-size: 2.5rem;
36
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
37
+ text-align: center;
38
+ }
39
+ .tubes-container {
40
+ display: flex;
41
+ flex-wrap: wrap;
42
+ justify-content: center;
43
+ gap: 20px;
44
+ width: 100%;
45
+ margin: 30px 0;
46
+ }
47
+ .tube {
48
+ width: 60px;
49
+ height: 200px;
50
+ background-color: rgba(255, 255, 255, 0.1);
51
+ border-radius: 5px 5px 0 0;
52
+ position: relative;
53
+ cursor: pointer;
54
+ border: 2px solid rgba(255, 255, 255, 0.3);
55
+ display: flex;
56
+ flex-direction: column-reverse;
57
+ align-items: center;
58
+ transition: transform 0.2s, box-shadow 0.2s;
59
+ margin-bottom: 40px;
60
+ }
61
+ .tube::after {
62
+ content: '';
63
+ position: absolute;
64
+ bottom: -20px;
65
+ left: -2px;
66
+ right: -2px;
67
+ height: 20px;
68
+ background-color: rgba(255, 255, 255, 0.1);
69
+ border-radius: 0 0 5px 5px;
70
+ border: 2px solid rgba(255, 255, 255, 0.3);
71
+ }
72
+ .tube:hover {
73
+ transform: translateY(-5px);
74
+ }
75
+ .tube.selected {
76
+ transform: scale(1.05);
77
+ box-shadow: 0 0 15px rgba(255, 255, 255, 0.7);
78
+ }
79
+ .tube.valid-move {
80
+ animation: pulse 1.5s infinite;
81
+ box-shadow: 0 0 15px rgba(0, 255, 0, 0.7);
82
+ }
83
+ @keyframes pulse {
84
+ 0% { box-shadow: 0 0 10px rgba(0, 255, 0, 0.7); }
85
+ 50% { box-shadow: 0 0 20px rgba(0, 255, 0, 0.9); }
86
+ 100% { box-shadow: 0 0 10px rgba(0, 255, 0, 0.7); }
87
+ }
88
+ .ball {
89
+ width: 50px;
90
+ height: 50px;
91
+ border-radius: 50%;
92
+ margin-top: 5px;
93
+ transition: all 0.3s ease;
94
+ position: relative;
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: center;
98
+ font-weight: bold;
99
+ color: rgba(0, 0, 0, 0.5);
100
+ box-shadow: inset -5px -5px 10px rgba(0, 0, 0, 0.2);
101
+ }
102
+ .ball::after {
103
+ content: '';
104
+ position: absolute;
105
+ top: 10px;
106
+ left: 10px;
107
+ width: 15px;
108
+ height: 15px;
109
+ border-radius: 50%;
110
+ background-color: rgba(255, 255, 255, 0.4);
111
+ }
112
+ .controls {
113
+ display: flex;
114
+ gap: 20px;
115
+ margin-top: 20px;
116
+ }
117
+ button {
118
+ padding: 10px 20px;
119
+ font-size: 1rem;
120
+ background-color: rgba(255, 255, 255, 0.2);
121
+ color: white;
122
+ border: 2px solid white;
123
+ border-radius: 5px;
124
+ cursor: pointer;
125
+ transition: all 0.3s;
126
+ text-transform: uppercase;
127
+ letter-spacing: 1px;
128
+ font-weight: bold;
129
+ }
130
+ button:hover {
131
+ background-color: rgba(255, 255, 255, 0.4);
132
+ transform: translateY(-2px);
133
+ }
134
+ .level-info {
135
+ margin-top: 20px;
136
+ font-size: 1.2rem;
137
+ text-align: center;
138
+ }
139
+ .message {
140
+ position: fixed;
141
+ top: 50%;
142
+ left: 50%;
143
+ transform: translate(-50%, -50%);
144
+ background-color: rgba(0, 0, 0, 0.8);
145
+ color: white;
146
+ padding: 30px 50px;
147
+ border-radius: 10px;
148
+ font-size: 2rem;
149
+ display: none;
150
+ z-index: 100;
151
+ text-align: center;
152
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
153
+ }
154
+ .message button {
155
+ margin-top: 20px;
156
+ display: block;
157
+ width: 100%;
158
+ }
159
+ .move-counter {
160
+ font-size: 1.2rem;
161
+ margin-bottom: 20px;
162
+ background-color: rgba(0, 0, 0, 0.3);
163
+ padding: 10px 20px;
164
+ border-radius: 5px;
165
+ }
166
+ .hint-container {
167
+ position: absolute;
168
+ top: 20px;
169
+ right: 20px;
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 10px;
173
+ }
174
+ .hint-count {
175
+ font-size: 0.9rem;
176
+ background-color: rgba(0, 0, 0, 0.3);
177
+ padding: 5px 10px;
178
+ border-radius: 5px;
179
+ text-align: center;
180
+ }
181
+ @media (max-width: 600px) {
182
+ .tube {
183
+ width: 50px;
184
+ height: 180px;
185
+ }
186
+ .ball {
187
+ width: 45px;
188
+ height: 45px;
189
+ }
190
+ h1 {
191
+ font-size: 1.8rem;
192
+ }
193
+ .hint-container {
194
+ position: static;
195
+ margin-bottom: 10px;
196
+ }
197
+ }
198
+ .floating-balls {
199
+ position: absolute;
200
+ top: 0;
201
+ left: 0;
202
+ width: 100%;
203
+ height: 100%;
204
+ pointer-events: none;
205
+ z-index: -1;
206
+ }
207
+ .floating-ball {
208
+ position: absolute;
209
+ border-radius: 50%;
210
+ opacity: 0.3;
211
+ animation: float 15s infinite linear;
212
+ }
213
+ @keyframes float {
214
+ 0% { transform: translate(0, 0) rotate(0deg); }
215
+ 25% { transform: translate(100px, 50px) rotate(90deg); }
216
+ 50% { transform: translate(200px, -50px) rotate(180deg); }
217
+ 75% { transform: translate(100px, 50px) rotate(270deg); }
218
+ 100% { transform: translate(0, 0) rotate(360deg); }
219
+ }
220
+ </style>
221
+ </head>
222
+ <body>
223
+ <div class="floating-balls" id="floatingBalls"></div>
224
+
225
+ <div class="game-container">
226
+ <div class="hint-container">
227
+ <button id="btnHint">Hint</button>
228
+ <div class="hint-count">Hints left: <span id="hintCount">3</span></div>
229
+ </div>
230
+
231
+ <h1>Ball Sort Puzzle</h1>
232
+ <div class="move-counter">Moves: <span id="moves">0</span></div>
233
+ <div class="tubes-container" id="tubes"></div>
234
+ <div class="level-info">Level: <span id="level">1</span></div>
235
+ <div class="controls">
236
+ <button id="btnNewGame">New Game</button>
237
+ <button id="btnUndo">Undo</button>
238
+ </div>
239
+ </div>
240
+
241
+ <div class="message" id="winMessage">
242
+ Level Complete!
243
+ <button id="btnNextLevel">Next Level</button>
244
+ </div>
245
+
246
+ <script>
247
+ // Game state
248
+ const gameState = {
249
+ tubes: [],
250
+ selectedTube: null,
251
+ moves: 0,
252
+ level: 1,
253
+ moveHistory: [],
254
+ hintsUsed: 0,
255
+ maxHints: 3
256
+ };
257
+
258
+ // Colors
259
+ const colors = [
260
+ '#FF5252', '#FF4081', '#E040FB', '#7C4DFF',
261
+ '#536DFE', '#448AFF', '#40C4FF', '#18FFFF',
262
+ '#64FFDA', '#69F0AE', '#B2FF59', '#EEFF41',
263
+ '#FFFF00', '#FFD740', '#FFAB40', '#FF6E40'
264
+ ];
265
+
266
+ // DOM elements
267
+ const tubesContainer = document.getElementById('tubes');
268
+ const movesDisplay = document.getElementById('moves');
269
+ const levelDisplay = document.getElementById('level');
270
+ const btnNewGame = document.getElementById('btnNewGame');
271
+ const btnUndo = document.getElementById('btnUndo');
272
+ const btnHint = document.getElementById('btnHint');
273
+ const hintCountDisplay = document.getElementById('hintCount');
274
+ const winMessage = document.getElementById('winMessage');
275
+ const btnNextLevel = document.getElementById('btnNextLevel');
276
+ const floatingBalls = document.getElementById('floatingBalls');
277
+
278
+ // Initialize floating background balls
279
+ function createFloatingBalls() {
280
+ floatingBalls.innerHTML = '';
281
+ for (let i = 0; i < 20; i++) {
282
+ const ball = document.createElement('div');
283
+ ball.className = 'floating-ball';
284
+ const size = Math.random() * 100 + 50;
285
+ ball.style.width = `${size}px`;
286
+ ball.style.height = `${size}px`;
287
+ ball.style.background = colors[Math.floor(Math.random() * colors.length)];
288
+ ball.style.left = `${Math.random() * 100}%`;
289
+ ball.style.top = `${Math.random() * 100}%`;
290
+ ball.style.animationDuration = `${Math.random() * 20 + 10}s`;
291
+ floatingBalls.appendChild(ball);
292
+ }
293
+ }
294
+
295
+ // Initialize game
296
+ function initGame() {
297
+ gameState.tubes = [];
298
+ gameState.selectedTube = null;
299
+ gameState.moves = 0;
300
+ gameState.moveHistory = [];
301
+ gameState.hintsUsed = 0;
302
+
303
+ movesDisplay.textContent = gameState.moves;
304
+ levelDisplay.textContent = gameState.level;
305
+ hintCountDisplay.textContent = gameState.maxHints - gameState.hintsUsed;
306
+
307
+ tubesContainer.innerHTML = '';
308
+ const tubeCount = Math.min(3 + gameState.level, 8);
309
+ const colorCount = Math.min(tubeCount - 2, colors.length);
310
+
311
+ // Create color set
312
+ const selectedColors = colors.slice(0, colorCount);
313
+ const allBalls = [];
314
+
315
+ // Create 4 balls for each color
316
+ selectedColors.forEach(color => {
317
+ for (let i = 0; i < 4; i++) {
318
+ allBalls.push(color);
319
+ }
320
+ });
321
+
322
+ // Shuffle balls
323
+ shuffleArray(allBalls);
324
+
325
+ // Distribute balls to tubes (first tubeCount - 2 tubes)
326
+ const ballsPerTube = allBalls.length / (tubeCount - 2);
327
+ for (let i = 0; i < tubeCount - 2; i++) {
328
+ const tubeBalls = [];
329
+ for (let j = 0; j < ballsPerTube; j++) {
330
+ tubeBalls.push(allBalls[i * ballsPerTube + j]);
331
+ }
332
+ gameState.tubes.push(tubeBalls);
333
+ }
334
+
335
+ // Add empty tubes (2)
336
+ for (let i = 0; i < 2; i++) {
337
+ gameState.tubes.push([]);
338
+ }
339
+
340
+ renderTubes();
341
+ }
342
+
343
+ // Helper function to shuffle an array
344
+ function shuffleArray(array) {
345
+ for (let i = array.length - 1; i > 0; i--) {
346
+ const j = Math.floor(Math.random() * (i + 1));
347
+ [array[i], array[j]] = [array[j], array[i]];
348
+ }
349
+ }
350
+
351
+ // Render tubes and balls
352
+ function renderTubes() {
353
+ tubesContainer.innerHTML = '';
354
+ gameState.tubes.forEach((tubeBalls, index) => {
355
+ const tubeElement = document.createElement('div');
356
+ tubeElement.className = 'tube';
357
+ if (gameState.selectedTube === index) {
358
+ tubeElement.classList.add('selected');
359
+ }
360
+
361
+ tubeElement.addEventListener('click', () => handleTubeClick(index));
362
+
363
+ tubeBalls.forEach(ballColor => {
364
+ const ballElement = document.createElement('div');
365
+ ballElement.className = 'ball';
366
+ ballElement.style.backgroundColor = ballColor;
367
+ tubeElement.appendChild(ballElement);
368
+ });
369
+
370
+ tubesContainer.appendChild(tubeElement);
371
+ });
372
+ }
373
+
374
+ // Show hint by highlighting valid move targets
375
+ function showHint() {
376
+ // Check if hints are available
377
+ if (gameState.hintsUsed >= gameState.maxHints) {
378
+ alert(`No hints left! You've used all ${gameState.maxHints} hints.`);
379
+ return;
380
+ }
381
+
382
+ gameState.hintsUsed++;
383
+ hintCountDisplay.textContent = gameState.maxHints - gameState.hintsUsed;
384
+
385
+ // First look for tubes where we can make progress (destination tubes)
386
+ for (let i = 0; i < gameState.tubes.length; i++) {
387
+ const tube = gameState.tubes[i];
388
+ if (tube.length > 0) {
389
+ const topColor = tube[tube.length - 1];
390
+ let count = 0;
391
+
392
+ // Count how many balls of same color are stacked
393
+ for (let j = tube.length - 1; j >= 0; j--) {
394
+ if (tube[j] === topColor) {
395
+ count++;
396
+ } else {
397
+ break;
398
+ }
399
+ }
400
+
401
+ // Find potential destination tubes
402
+ const potentialTargets = [];
403
+ for (let j = 0; j < gameState.tubes.length; j++) {
404
+ if (i === j) continue;
405
+ const targetTube = gameState.tubes[j];
406
+
407
+ // Empty tube is always valid
408
+ if (targetTube.length === 0) {
409
+ potentialTargets.push(j);
410
+ }
411
+ // Tube with same color top ball and space available
412
+ else if (targetTube[targetTube.length - 1] === topColor &&
413
+ (targetTube.length + count) <= 4) {
414
+ potentialTargets.push(j);
415
+ }
416
+ }
417
+
418
+ // If we found valid targets, highlight them
419
+ if (potentialTargets.length > 0) {
420
+ // Highlight source tube
421
+ gameState.selectedTube = i;
422
+ renderTubes();
423
+
424
+ // Highlight target tubes
425
+ const tubeElements = document.querySelectorAll('.tube');
426
+ potentialTargets.forEach(targetIndex => {
427
+ tubeElements[targetIndex].classList.add('valid-move');
428
+ });
429
+
430
+ // Remove highlights after 3 seconds
431
+ setTimeout(() => {
432
+ gameState.selectedTube = null;
433
+ renderTubes();
434
+ const validMoves = document.querySelectorAll('.valid-move');
435
+ validMoves.forEach(tube => {
436
+ tube.classList.remove('valid-move');
437
+ });
438
+ }, 3000);
439
+
440
+ return; // Show just one hint at a time
441
+ }
442
+ }
443
+ }
444
+ }
445
+
446
+ // Handle tube click
447
+ function handleTubeClick(tubeIndex) {
448
+ if (gameState.selectedTube === null) {
449
+ // Select a tube if it has balls
450
+ if (gameState.tubes[tubeIndex].length > 0) {
451
+ gameState.selectedTube = tubeIndex;
452
+ renderTubes();
453
+ }
454
+ } else {
455
+ // Try to move ball from selected tube to clicked tube
456
+ if (gameState.selectedTube === tubeIndex) {
457
+ // Deselect if same tube clicked
458
+ gameState.selectedTube = null;
459
+ renderTubes();
460
+ } else {
461
+ moveBall(gameState.selectedTube, tubeIndex);
462
+ }
463
+ }
464
+ }
465
+
466
+ // Animate moving the top ball from one tube to another
467
+ function animateBallMove(fromTubeIndex, toTubeIndex, ballColor) {
468
+ // Get source tube element and its last ball element
469
+ const fromTubeElement = tubesContainer.children[fromTubeIndex];
470
+ const ballElement = fromTubeElement.lastElementChild;
471
+ if (!ballElement) return;
472
+
473
+ // Get destination tube element
474
+ const toTubeElement = tubesContainer.children[toTubeIndex];
475
+
476
+ // Get bounding rectangles for start and target positions
477
+ const fromRect = ballElement.getBoundingClientRect();
478
+ const toRect = toTubeElement.getBoundingClientRect();
479
+
480
+ // Determine target position.
481
+ // We use the number of balls already in the destination to offset vertically.
482
+ const targetBallIndex = gameState.tubes[toTubeIndex].length;
483
+ const ballHeight = ballElement.offsetHeight;
484
+ // Adjust target position: center the ball horizontally and stack from the bottom
485
+ const targetX = toRect.left + toRect.width / 2 - fromRect.width / 2;
486
+ const targetY = toRect.bottom - (targetBallIndex + 1) * (ballHeight + 5);
487
+
488
+ // Create a clone of the ball element for animation
489
+ const clone = ballElement.cloneNode(true);
490
+ clone.style.position = 'absolute';
491
+ clone.style.left = fromRect.left + 'px';
492
+ clone.style.top = fromRect.top + 'px';
493
+ clone.style.margin = '0';
494
+ clone.style.transition = 'all 0.5s ease-in-out';
495
+ document.body.appendChild(clone);
496
+
497
+ // Hide the original ball temporarily
498
+ ballElement.style.visibility = 'hidden';
499
+
500
+ // Start animation on next frame
501
+ requestAnimationFrame(() => {
502
+ clone.style.left = targetX + 'px';
503
+ clone.style.top = targetY + 'px';
504
+ });
505
+
506
+ // When animation completes, update the game state and re-render
507
+ clone.addEventListener('transitionend', () => {
508
+ clone.remove();
509
+ // Determine how many balls of the same color to move (if they are stacked)
510
+ let ballsToMove = 0;
511
+ const fromTube = gameState.tubes[fromTubeIndex];
512
+ for (let i = fromTube.length - 1; i >= 0; i--) {
513
+ if (fromTube[i] === ballColor && (gameState.tubes[toTubeIndex].length + ballsToMove) < 4) {
514
+ ballsToMove++;
515
+ } else {
516
+ break;
517
+ }
518
+ }
519
+ // Save current state for undo
520
+ const prevState = JSON.parse(JSON.stringify(gameState.tubes));
521
+ gameState.moveHistory.push(prevState);
522
+ // Move the balls in game state
523
+ const movedBalls = gameState.tubes[fromTubeIndex].splice(fromTube.length - ballsToMove, ballsToMove);
524
+ gameState.tubes[toTubeIndex].push(...movedBalls);
525
+
526
+ gameState.moves++;
527
+ movesDisplay.textContent = gameState.moves;
528
+ gameState.selectedTube = null;
529
+ renderTubes();
530
+
531
+ // Check win condition after state update
532
+ if (checkWin()) {
533
+ showWinMessage();
534
+ }
535
+ });
536
+ }
537
+
538
+ // Move ball from one tube to another (with animation)
539
+ function moveBall(fromTubeIndex, toTubeIndex) {
540
+ const fromTube = gameState.tubes[fromTubeIndex];
541
+ const toTube = gameState.tubes[toTubeIndex];
542
+
543
+ if (fromTube.length === 0) {
544
+ gameState.selectedTube = null;
545
+ renderTubes();
546
+ return;
547
+ }
548
+
549
+ const topBallColor = fromTube[fromTube.length - 1];
550
+
551
+ // Move is valid if destination is empty OR has the same color and is not full
552
+ if (toTube.length === 0 || (toTube[toTube.length - 1] === topBallColor && toTube.length < 4)) {
553
+ // Start the animation; state will update once the animation completes
554
+ animateBallMove(fromTubeIndex, toTubeIndex, topBallColor);
555
+ } else {
556
+ gameState.selectedTube = null;
557
+ renderTubes();
558
+ }
559
+ }
560
+
561
+ // Check if all tubes are complete (all balls of same color or empty)
562
+ function checkWin() {
563
+ return gameState.tubes.every(tube => {
564
+ if (tube.length === 0) return true;
565
+ if (tube.length < 4) return false;
566
+ const firstColor = tube[0];
567
+ return tube.every(ball => ball === firstColor);
568
+ });
569
+ }
570
+
571
+ // Show win message
572
+ function showWinMessage() {
573
+ winMessage.style.display = 'block';
574
+ }
575
+
576
+ // Undo last move
577
+ function undoMove() {
578
+ if (gameState.moveHistory.length > 0) {
579
+ gameState.tubes = gameState.moveHistory.pop();
580
+ gameState.moves++;
581
+ movesDisplay.textContent = gameState.moves;
582
+ gameState.selectedTube = null;
583
+ renderTubes();
584
+ }
585
+ }
586
+
587
+ // Next level
588
+ function nextLevel() {
589
+ gameState.level++;
590
+ winMessage.style.display = 'none';
591
+ initGame();
592
+ }
593
+
594
+ // Event listeners
595
+ btnNewGame.addEventListener('click', initGame);
596
+ btnUndo.addEventListener('click', undoMove);
597
+ btnHint.addEventListener('click', showHint);
598
+ btnNextLevel.addEventListener('click', nextLevel);
599
+
600
+ // Initialize
601
+ createFloatingBalls();
602
+ initGame();
603
+
604
+ // Add keyboard controls
605
+ document.addEventListener('keydown', (e) => {
606
+ if (e.key === 'Escape') {
607
+ gameState.selectedTube = null;
608
+ renderTubes();
609
+ } else if (e.key === 'z' && (e.ctrlKey || e.metaKey)) {
610
+ undoMove();
611
+ } else if (e.key === 'h') {
612
+ showHint();
613
+ }
614
+ });
615
+ </script>
616
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
617
+ </html>