wangjinrambo commited on
Commit
f99ebd9
·
verified ·
1 Parent(s): 32a3be1

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +712 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: T Boy
3
- emoji:
4
- colorFrom: purple
5
- colorTo: blue
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: t-boy
3
+ emoji: 🐳
4
+ colorFrom: yellow
5
+ colorTo: pink
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,712 @@
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>Neon Tetris</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @keyframes explode {
10
+ 0% { transform: scale(1); opacity: 1; }
11
+ 100% { transform: scale(3); opacity: 0; }
12
+ }
13
+
14
+ @keyframes pulse {
15
+ 0%, 100% { transform: scale(1); }
16
+ 50% { transform: scale(1.1); }
17
+ }
18
+
19
+ @keyframes rainbow {
20
+ 0% { background-color: #ff0000; }
21
+ 14% { background-color: #ff7f00; }
22
+ 28% { background-color: #ffff00; }
23
+ 42% { background-color: #00ff00; }
24
+ 57% { background-color: #0000ff; }
25
+ 71% { background-color: #4b0082; }
26
+ 85% { background-color: #9400d3; }
27
+ 100% { background-color: #ff0000; }
28
+ }
29
+
30
+ .explosion {
31
+ animation: explode 0.5s forwards;
32
+ position: absolute;
33
+ border-radius: 50%;
34
+ pointer-events: none;
35
+ }
36
+
37
+ .cleared-row {
38
+ animation: pulse 0.2s 3, rainbow 1.5s;
39
+ }
40
+
41
+ .tetris-grid {
42
+ box-shadow: 0 0 20px rgba(0, 255, 255, 0.7);
43
+ }
44
+
45
+ .tetris-cell {
46
+ transition: background-color 0.1s;
47
+ box-shadow: inset 0 0 5px rgba(255, 255, 255, 0.3);
48
+ }
49
+
50
+ .ghost-piece {
51
+ opacity: 0.3;
52
+ }
53
+
54
+ .next-piece-container {
55
+ box-shadow: 0 0 15px rgba(255, 0, 255, 0.5);
56
+ }
57
+ </style>
58
+ </head>
59
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 font-mono">
60
+ <div class="flex flex-col md:flex-row items-center gap-8">
61
+ <!-- Game board -->
62
+ <div class="relative">
63
+ <div class="tetris-grid bg-gray-800 border-2 border-cyan-400 rounded">
64
+ <div id="game-board" class="grid grid-cols-10 grid-rows-20 gap-0"></div>
65
+ </div>
66
+ <div id="game-over" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center hidden">
67
+ <h2 class="text-4xl font-bold mb-4 text-red-500">GAME OVER</h2>
68
+ <button id="restart-btn" class="px-6 py-2 bg-cyan-500 hover:bg-cyan-600 rounded-lg font-bold transition">Play Again</button>
69
+ </div>
70
+ </div>
71
+
72
+ <!-- Side panel -->
73
+ <div class="flex flex-col gap-6">
74
+ <div class="bg-gray-800 p-4 rounded-lg border-2 border-purple-400">
75
+ <h2 class="text-xl font-bold mb-2 text-center text-purple-300">NEXT</h2>
76
+ <div class="next-piece-container bg-gray-700 p-2 rounded">
77
+ <div id="next-piece" class="grid grid-cols-4 grid-rows-4 gap-1 w-24 h-24"></div>
78
+ </div>
79
+ </div>
80
+
81
+ <div class="bg-gray-800 p-4 rounded-lg border-2 border-green-400">
82
+ <h2 class="text-xl font-bold mb-2 text-center text-green-300">SCORE</h2>
83
+ <div class="text-3xl font-bold text-center" id="score">0</div>
84
+ </div>
85
+
86
+ <div class="bg-gray-800 p-4 rounded-lg border-2 border-yellow-400">
87
+ <h2 class="text-xl font-bold mb-2 text-center text-yellow-300">LEVEL</h2>
88
+ <div class="text-3xl font-bold text-center" id="level">1</div>
89
+ </div>
90
+
91
+ <div class="bg-gray-800 p-4 rounded-lg border-2 border-red-400">
92
+ <h2 class="text-xl font-bold mb-2 text-center text-red-300">LINES</h2>
93
+ <div class="text-3xl font-bold text-center" id="lines">0</div>
94
+ </div>
95
+
96
+ <div class="flex flex-col gap-2 mt-2">
97
+ <button id="pause-btn" class="px-4 py-2 bg-yellow-500 hover:bg-yellow-600 rounded font-bold transition">Pause</button>
98
+ <button id="sound-btn" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 rounded font-bold transition">Sound: ON</button>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- Controls info -->
104
+ <div class="mt-8 text-center text-gray-400">
105
+ <p class="mb-2">Controls: Arrow keys to move, Up to rotate, Space to drop, P to pause</p>
106
+ <p>Made with HTML, CSS & JavaScript</p>
107
+ </div>
108
+
109
+ <script>
110
+ document.addEventListener('DOMContentLoaded', () => {
111
+ // Game constants
112
+ const COLS = 10;
113
+ const ROWS = 20;
114
+ const BLOCK_SIZE = 30;
115
+ const EMPTY = 'empty';
116
+
117
+ // Scoring
118
+ const SCORE = {
119
+ SINGLE: 100,
120
+ DOUBLE: 300,
121
+ TRIPLE: 500,
122
+ TETRIS: 800,
123
+ SOFT_DROP: 1,
124
+ HARD_DROP: 2
125
+ };
126
+
127
+ const LEVEL_SPEED = {
128
+ 1: 800,
129
+ 2: 720,
130
+ 3: 630,
131
+ 4: 550,
132
+ 5: 470,
133
+ 6: 380,
134
+ 7: 300,
135
+ 8: 220,
136
+ 9: 130,
137
+ 10: 100,
138
+ 11: 80,
139
+ 12: 80,
140
+ 13: 80,
141
+ 14: 70,
142
+ 15: 70,
143
+ 16: 70,
144
+ 17: 50,
145
+ 18: 50,
146
+ 19: 50,
147
+ 20: 30
148
+ };
149
+
150
+ // Tetromino shapes and colors
151
+ const SHAPES = [
152
+ { shape: [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], color: 'bg-cyan-500' }, // I
153
+ { shape: [[1, 0, 0], [1, 1, 1], [0, 0, 0]], color: 'bg-blue-500' }, // J
154
+ { shape: [[0, 0, 1], [1, 1, 1], [0, 0, 0]], color: 'bg-orange-500' }, // L
155
+ { shape: [[1, 1], [1, 1]], color: 'bg-yellow-500' }, // O
156
+ { shape: [[0, 1, 1], [1, 1, 0], [0, 0, 0]], color: 'bg-green-500' }, // S
157
+ { shape: [[0, 1, 0], [1, 1, 1], [0, 0, 0]], color: 'bg-purple-500' }, // T
158
+ { shape: [[1, 1, 0], [0, 1, 1], [0, 0, 0]], color: 'bg-red-500' } // Z
159
+ ];
160
+
161
+ // Game state
162
+ let board = Array(ROWS).fill().map(() => Array(COLS).fill(EMPTY));
163
+ let currentPiece = null;
164
+ let nextPiece = null;
165
+ let currentPosition = { x: 0, y: 0 };
166
+ let score = 0;
167
+ let lines = 0;
168
+ let level = 1;
169
+ let gameOver = false;
170
+ let isPaused = false;
171
+ let soundEnabled = true;
172
+ let dropInterval = null;
173
+ let ghostPiece = null;
174
+
175
+ // DOM elements
176
+ const gameBoard = document.getElementById('game-board');
177
+ const nextPieceDisplay = document.getElementById('next-piece');
178
+ const scoreDisplay = document.getElementById('score');
179
+ const linesDisplay = document.getElementById('lines');
180
+ const levelDisplay = document.getElementById('level');
181
+ const gameOverDisplay = document.getElementById('game-over');
182
+ const restartBtn = document.getElementById('restart-btn');
183
+ const pauseBtn = document.getElementById('pause-btn');
184
+ const soundBtn = document.getElementById('sound-btn');
185
+
186
+ // Initialize game board
187
+ function initBoard() {
188
+ gameBoard.innerHTML = '';
189
+ gameBoard.style.width = `${COLS * BLOCK_SIZE}px`;
190
+ gameBoard.style.height = `${ROWS * BLOCK_SIZE}px`;
191
+
192
+ for (let y = 0; y < ROWS; y++) {
193
+ for (let x = 0; x < COLS; x++) {
194
+ const cell = document.createElement('div');
195
+ cell.className = 'tetris-cell w-7 h-7 border border-gray-700';
196
+ cell.dataset.row = y;
197
+ cell.dataset.col = x;
198
+ gameBoard.appendChild(cell);
199
+ }
200
+ }
201
+ }
202
+
203
+ // Create a random tetromino
204
+ function createPiece() {
205
+ const randomIndex = Math.floor(Math.random() * SHAPES.length);
206
+ return {
207
+ shape: SHAPES[randomIndex].shape,
208
+ color: SHAPES[randomIndex].color,
209
+ width: SHAPES[randomIndex].shape[0].length,
210
+ height: SHAPES[randomIndex].shape.length
211
+ };
212
+ }
213
+
214
+ // Draw the game board
215
+ function drawBoard() {
216
+ const cells = document.querySelectorAll('.tetris-cell');
217
+
218
+ // Clear all cells
219
+ cells.forEach(cell => {
220
+ cell.className = 'tetris-cell w-7 h-7 border border-gray-700';
221
+ cell.innerHTML = '';
222
+ });
223
+
224
+ // Draw locked pieces
225
+ for (let y = 0; y < ROWS; y++) {
226
+ for (let x = 0; x < COLS; x++) {
227
+ if (board[y][x] !== EMPTY) {
228
+ const cell = document.querySelector(`[data-row="${y}"][data-col="${x}"]`);
229
+ cell.className = `tetris-cell w-7 h-7 border border-gray-700 ${board[y][x]}`;
230
+ }
231
+ }
232
+ }
233
+
234
+ // Draw current piece
235
+ if (currentPiece) {
236
+ for (let y = 0; y < currentPiece.height; y++) {
237
+ for (let x = 0; x < currentPiece.width; x++) {
238
+ if (currentPiece.shape[y][x]) {
239
+ const boardX = currentPosition.x + x;
240
+ const boardY = currentPosition.y + y;
241
+
242
+ if (boardY >= 0) {
243
+ const cell = document.querySelector(`[data-row="${boardY}"][data-col="${boardX}"]`);
244
+ if (cell) {
245
+ cell.className = `tetris-cell w-7 h-7 border border-gray-700 ${currentPiece.color}`;
246
+ }
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+
253
+ // Draw ghost piece
254
+ if (ghostPiece) {
255
+ for (let y = 0; y < ghostPiece.height; y++) {
256
+ for (let x = 0; x < ghostPiece.width; x++) {
257
+ if (ghostPiece.shape[y][x]) {
258
+ const boardX = ghostPiece.position.x + x;
259
+ const boardY = ghostPiece.position.y + y;
260
+
261
+ if (boardY >= 0) {
262
+ const cell = document.querySelector(`[data-row="${boardY}"][data-col="${boardX}"]`);
263
+ if (cell) {
264
+ cell.classList.add('ghost-piece', currentPiece.color);
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ // Draw next piece preview
274
+ function drawNextPiece() {
275
+ nextPieceDisplay.innerHTML = '';
276
+
277
+ for (let y = 0; y < 4; y++) {
278
+ for (let x = 0; x < 4; x++) {
279
+ const cell = document.createElement('div');
280
+ cell.className = 'w-5 h-5 border border-gray-600';
281
+
282
+ if (nextPiece && y < nextPiece.height && x < nextPiece.width && nextPiece.shape[y][x]) {
283
+ cell.className += ` ${nextPiece.color}`;
284
+ }
285
+
286
+ nextPieceDisplay.appendChild(cell);
287
+ }
288
+ }
289
+ }
290
+
291
+ // Check for collisions
292
+ function collision(x, y, piece) {
293
+ for (let py = 0; py < piece.height; py++) {
294
+ for (let px = 0; px < piece.width; px++) {
295
+ if (piece.shape[py][px]) {
296
+ const boardX = x + px;
297
+ const boardY = y + py;
298
+
299
+ // Check boundaries
300
+ if (boardX < 0 || boardX >= COLS || boardY >= ROWS) {
301
+ return true;
302
+ }
303
+
304
+ // Check if already occupied (and not above the board)
305
+ if (boardY >= 0 && board[boardY][boardX] !== EMPTY) {
306
+ return true;
307
+ }
308
+ }
309
+ }
310
+ }
311
+ return false;
312
+ }
313
+
314
+ // Rotate the current piece
315
+ function rotate() {
316
+ if (!currentPiece || gameOver || isPaused) return;
317
+
318
+ const rotated = {
319
+ shape: Array(currentPiece.width).fill().map(() => Array(currentPiece.height).fill(0)),
320
+ color: currentPiece.color,
321
+ width: currentPiece.height,
322
+ height: currentPiece.width
323
+ };
324
+
325
+ // Transpose and reverse rows to rotate 90 degrees
326
+ for (let y = 0; y < currentPiece.height; y++) {
327
+ for (let x = 0; x < currentPiece.width; x++) {
328
+ rotated.shape[x][currentPiece.height - 1 - y] = currentPiece.shape[y][x];
329
+ }
330
+ }
331
+
332
+ // Check if rotation is possible
333
+ if (!collision(currentPosition.x, currentPosition.y, rotated)) {
334
+ currentPiece = rotated;
335
+ updateGhostPiece();
336
+ drawBoard();
337
+ playSound('rotate');
338
+ } else {
339
+ // Try wall kicks (move left/right if rotation hits wall)
340
+ const kicks = [-1, 1, -2, 2];
341
+ for (const kick of kicks) {
342
+ if (!collision(currentPosition.x + kick, currentPosition.y, rotated)) {
343
+ currentPosition.x += kick;
344
+ currentPiece = rotated;
345
+ updateGhostPiece();
346
+ drawBoard();
347
+ playSound('rotate');
348
+ break;
349
+ }
350
+ }
351
+ }
352
+ }
353
+
354
+ // Move the piece left/right
355
+ function move(direction) {
356
+ if (!currentPiece || gameOver || isPaused) return;
357
+
358
+ const newX = currentPosition.x + direction;
359
+
360
+ if (!collision(newX, currentPosition.y, currentPiece)) {
361
+ currentPosition.x = newX;
362
+ updateGhostPiece();
363
+ drawBoard();
364
+ if (direction !== 0) playSound('move');
365
+ }
366
+ }
367
+
368
+ // Move the piece down (soft drop)
369
+ function softDrop() {
370
+ if (!currentPiece || gameOver || isPaused) return;
371
+
372
+ const newY = currentPosition.y + 1;
373
+
374
+ if (!collision(currentPosition.x, newY, currentPiece)) {
375
+ currentPosition.y = newY;
376
+ updateGhostPiece();
377
+ drawBoard();
378
+ addScore(SCORE.SOFT_DROP);
379
+ return false; // Didn't hit bottom
380
+ } else {
381
+ lockPiece();
382
+ return true; // Hit bottom
383
+ }
384
+ }
385
+
386
+ // Hard drop (instant drop)
387
+ function hardDrop() {
388
+ if (!currentPiece || gameOver || isPaused) return;
389
+
390
+ let dropDistance = 0;
391
+
392
+ while (!collision(currentPosition.x, currentPosition.y + 1, currentPiece)) {
393
+ currentPosition.y++;
394
+ dropDistance++;
395
+ }
396
+
397
+ lockPiece();
398
+ addScore(SCORE.HARD_DROP * dropDistance);
399
+ playSound('hardDrop');
400
+ }
401
+
402
+ // Lock the piece in place
403
+ function lockPiece() {
404
+ for (let y = 0; y < currentPiece.height; y++) {
405
+ for (let x = 0; x < currentPiece.width; x++) {
406
+ if (currentPiece.shape[y][x]) {
407
+ const boardY = currentPosition.y + y;
408
+ const boardX = currentPosition.x + x;
409
+
410
+ // If piece is locked above the board, game over
411
+ if (boardY < 0) {
412
+ endGame();
413
+ return;
414
+ }
415
+
416
+ board[boardY][boardX] = currentPiece.color;
417
+ }
418
+ }
419
+ }
420
+
421
+ // Check for completed lines
422
+ checkLines();
423
+
424
+ // Get next piece
425
+ spawnPiece();
426
+ playSound('lock');
427
+ }
428
+
429
+ // Check for completed lines
430
+ function checkLines() {
431
+ const linesToClear = [];
432
+
433
+ for (let y = ROWS - 1; y >= 0; y--) {
434
+ if (board[y].every(cell => cell !== EMPTY)) {
435
+ linesToClear.push(y);
436
+ }
437
+ }
438
+
439
+ if (linesToClear.length > 0) {
440
+ clearLines(linesToClear);
441
+ }
442
+ }
443
+
444
+ // Clear completed lines with special effects
445
+ function clearLines(linesToClear) {
446
+ // Highlight lines about to be cleared
447
+ linesToClear.forEach(y => {
448
+ for (let x = 0; x < COLS; x++) {
449
+ const cell = document.querySelector(`[data-row="${y}"][data-col="${x}"]`);
450
+ if (cell) cell.classList.add('cleared-row');
451
+ }
452
+ });
453
+
454
+ playSound('clear');
455
+
456
+ // Wait for animation then clear
457
+ setTimeout(() => {
458
+ // Create explosion effects
459
+ linesToClear.forEach(y => {
460
+ for (let x = 0; x < COLS; x++) {
461
+ createExplosion(x, y);
462
+ }
463
+ });
464
+
465
+ // Remove lines and add new empty ones at top
466
+ linesToClear.forEach(y => {
467
+ board.splice(y, 1);
468
+ board.unshift(Array(COLS).fill(EMPTY));
469
+ });
470
+
471
+ // Update score
472
+ addScore(
473
+ linesToClear.length === 1 ? SCORE.SINGLE * level :
474
+ linesToClear.length === 2 ? SCORE.DOUBLE * level :
475
+ linesToClear.length === 3 ? SCORE.TRIPLE * level :
476
+ SCORE.TETRIS * level
477
+ );
478
+
479
+ // Update lines and level
480
+ lines += linesToClear.length;
481
+ linesDisplay.textContent = lines;
482
+
483
+ const newLevel = Math.floor(lines / 10) + 1;
484
+ if (newLevel > level && newLevel <= 20) {
485
+ level = newLevel;
486
+ levelDisplay.textContent = level;
487
+ updateDropSpeed();
488
+ playSound('levelUp');
489
+ }
490
+
491
+ drawBoard();
492
+ }, 300);
493
+ }
494
+
495
+ // Create explosion effect at a cell
496
+ function createExplosion(x, y) {
497
+ const cell = document.querySelector(`[data-row="${y}"][data-col="${x}"]`);
498
+ if (!cell) return;
499
+
500
+ const rect = cell.getBoundingClientRect();
501
+ const colors = [
502
+ 'bg-red-500', 'bg-orange-500', 'bg-yellow-500',
503
+ 'bg-green-500', 'bg-blue-500', 'bg-purple-500'
504
+ ];
505
+
506
+ // Create multiple explosion particles
507
+ for (let i = 0; i < 5; i++) {
508
+ const particle = document.createElement('div');
509
+ const color = colors[Math.floor(Math.random() * colors.length)];
510
+
511
+ particle.className = `explosion ${color} w-3 h-3`;
512
+ particle.style.left = `${rect.left + rect.width / 2}px`;
513
+ particle.style.top = `${rect.top + rect.height / 2}px`;
514
+
515
+ document.body.appendChild(particle);
516
+
517
+ // Remove after animation
518
+ setTimeout(() => {
519
+ particle.remove();
520
+ }, 500);
521
+ }
522
+ }
523
+
524
+ // Spawn a new piece
525
+ function spawnPiece() {
526
+ currentPiece = nextPiece || createPiece();
527
+ nextPiece = createPiece();
528
+ currentPosition = { x: Math.floor(COLS / 2) - Math.floor(currentPiece.width / 2), y: -2 };
529
+
530
+ // Check if game over (new piece collides immediately)
531
+ if (collision(currentPosition.x, currentPosition.y, currentPiece)) {
532
+ endGame();
533
+ }
534
+
535
+ updateGhostPiece();
536
+ drawBoard();
537
+ drawNextPiece();
538
+ }
539
+
540
+ // Update ghost piece position
541
+ function updateGhostPiece() {
542
+ if (!currentPiece) return;
543
+
544
+ ghostPiece = {
545
+ shape: currentPiece.shape,
546
+ color: currentPiece.color,
547
+ width: currentPiece.width,
548
+ height: currentPiece.height,
549
+ position: { ...currentPosition }
550
+ };
551
+
552
+ // Drop ghost to lowest possible position
553
+ while (!collision(ghostPiece.position.x, ghostPiece.position.y + 1, ghostPiece)) {
554
+ ghostPiece.position.y++;
555
+ }
556
+ }
557
+
558
+ // Update drop speed based on level
559
+ function updateDropSpeed() {
560
+ if (dropInterval) {
561
+ clearInterval(dropInterval);
562
+ }
563
+
564
+ dropInterval = setInterval(() => {
565
+ softDrop();
566
+ }, LEVEL_SPEED[level]);
567
+ }
568
+
569
+ // Add to score
570
+ function addScore(points) {
571
+ score += points;
572
+ scoreDisplay.textContent = score;
573
+ }
574
+
575
+ // End the game
576
+ function endGame() {
577
+ gameOver = true;
578
+ clearInterval(dropInterval);
579
+ gameOverDisplay.classList.remove('hidden');
580
+ playSound('gameOver');
581
+ }
582
+
583
+ // Reset the game
584
+ function resetGame() {
585
+ board = Array(ROWS).fill().map(() => Array(COLS).fill(EMPTY));
586
+ score = 0;
587
+ lines = 0;
588
+ level = 1;
589
+ gameOver = false;
590
+
591
+ scoreDisplay.textContent = score;
592
+ linesDisplay.textContent = lines;
593
+ levelDisplay.textContent = level;
594
+
595
+ gameOverDisplay.classList.add('hidden');
596
+
597
+ nextPiece = createPiece();
598
+ spawnPiece();
599
+ updateDropSpeed();
600
+
601
+ drawBoard();
602
+ drawNextPiece();
603
+ }
604
+
605
+ // Play sound effects
606
+ function playSound(type) {
607
+ if (!soundEnabled) return;
608
+
609
+ const sounds = {
610
+ rotate: () => {
611
+ const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
612
+ audio.volume = 0.3;
613
+ audio.play();
614
+ },
615
+ move: () => {
616
+ const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
617
+ audio.volume = 0.2;
618
+ audio.play();
619
+ },
620
+ hardDrop: () => {
621
+ const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
622
+ audio.volume = 0.4;
623
+ audio.play();
624
+ },
625
+ lock: () => {
626
+ const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
627
+ audio.volume = 0.3;
628
+ audio.play();
629
+ },
630
+ clear: () => {
631
+ const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
632
+ audio.volume = 0.5;
633
+ audio.play();
634
+ },
635
+ levelUp: () => {
636
+ const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
637
+ audio.volume = 0.5;
638
+ audio.play();
639
+ },
640
+ gameOver: () => {
641
+ const audio = new Audio('data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU...');
642
+ audio.volume = 0.6;
643
+ audio.play();
644
+ }
645
+ };
646
+
647
+ if (sounds[type]) {
648
+ sounds[type]();
649
+ }
650
+ }
651
+
652
+ // Toggle pause
653
+ function togglePause() {
654
+ isPaused = !isPaused;
655
+ pauseBtn.textContent = isPaused ? 'Resume' : 'Pause';
656
+
657
+ if (isPaused) {
658
+ clearInterval(dropInterval);
659
+ playSound('pause');
660
+ } else {
661
+ updateDropSpeed();
662
+ playSound('resume');
663
+ }
664
+ }
665
+
666
+ // Toggle sound
667
+ function toggleSound() {
668
+ soundEnabled = !soundEnabled;
669
+ soundBtn.textContent = `Sound: ${soundEnabled ? 'ON' : 'OFF'}`;
670
+ }
671
+
672
+ // Keyboard controls
673
+ document.addEventListener('keydown', (e) => {
674
+ if (gameOver) return;
675
+
676
+ switch (e.key) {
677
+ case 'ArrowLeft':
678
+ move(-1);
679
+ break;
680
+ case 'ArrowRight':
681
+ move(1);
682
+ break;
683
+ case 'ArrowDown':
684
+ softDrop();
685
+ break;
686
+ case 'ArrowUp':
687
+ rotate();
688
+ break;
689
+ case ' ':
690
+ hardDrop();
691
+ break;
692
+ case 'p':
693
+ case 'P':
694
+ togglePause();
695
+ break;
696
+ }
697
+ });
698
+
699
+ // Button controls
700
+ restartBtn.addEventListener('click', resetGame);
701
+ pauseBtn.addEventListener('click', togglePause);
702
+ soundBtn.addEventListener('click', toggleSound);
703
+
704
+ // Initialize game
705
+ initBoard();
706
+ nextPiece = createPiece();
707
+ spawnPiece();
708
+ updateDropSpeed();
709
+ });
710
+ </script>
711
+ <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 <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=wangjinrambo/t-boy" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
712
+ </html>