Spaces:
Running
Running
Add 1 files
Browse files- index.html +146 -46
index.html
CHANGED
@@ -55,52 +55,75 @@
|
|
55 |
transform: scale(1.05);
|
56 |
box-shadow: 0 0 15px rgba(255, 255, 255, 0.7);
|
57 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
</style>
|
59 |
</head>
|
60 |
<body class="min-h-screen flex flex-col items-center justify-center text-white overflow-hidden p-4">
|
61 |
-
<div class="
|
62 |
-
<
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
<div>
|
67 |
-
<
|
68 |
-
|
69 |
-
|
70 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
</div>
|
72 |
</div>
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
94 |
</div>
|
95 |
</div>
|
96 |
</div>
|
97 |
|
98 |
-
<div class="mt-4 flex gap-4">
|
99 |
-
<button id="sound-toggle" class="px-4 py-2 bg-white bg-opacity-20 rounded-lg hover:bg-opacity-30 transition">
|
100 |
-
π Sound On
|
101 |
-
</button>
|
102 |
-
</div>
|
103 |
-
|
104 |
<audio id="bg-music" loop>
|
105 |
<source src="https://music.mota.press/music/tetris/project/bgms/bgm.mp3" type="audio/mpeg">
|
106 |
</audio>
|
@@ -141,7 +164,7 @@
|
|
141 |
// Game settings
|
142 |
const COLS = 10;
|
143 |
const ROWS = 20;
|
144 |
-
const BLOCK_SIZE =
|
145 |
const COLORS = [
|
146 |
null,
|
147 |
'#00d2d3', // I
|
@@ -232,18 +255,36 @@
|
|
232 |
|
233 |
// Draw the game board
|
234 |
function drawBoard() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
236 |
|
|
|
|
|
|
|
|
|
237 |
// Draw existing blocks
|
238 |
board.forEach((row, y) => {
|
239 |
row.forEach((value, x) => {
|
240 |
if (value) {
|
241 |
ctx.fillStyle = COLORS[value];
|
242 |
-
ctx.fillRect(x *
|
243 |
|
244 |
// Add border to blocks
|
245 |
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
246 |
-
ctx.strokeRect(x *
|
247 |
}
|
248 |
});
|
249 |
});
|
@@ -255,17 +296,17 @@
|
|
255 |
if (value) {
|
256 |
ctx.fillStyle = COLORS[value];
|
257 |
ctx.fillRect(
|
258 |
-
(piece.pos.x + x) *
|
259 |
-
(piece.pos.y + y) *
|
260 |
-
|
261 |
);
|
262 |
|
263 |
// Add border to current piece
|
264 |
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
265 |
ctx.strokeRect(
|
266 |
-
(piece.pos.x + x) *
|
267 |
-
(piece.pos.y + y) *
|
268 |
-
|
269 |
);
|
270 |
}
|
271 |
});
|
@@ -517,6 +558,65 @@
|
|
517 |
}
|
518 |
});
|
519 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
520 |
// Initial draw
|
521 |
drawBoard();
|
522 |
});
|
|
|
55 |
transform: scale(1.05);
|
56 |
box-shadow: 0 0 15px rgba(255, 255, 255, 0.7);
|
57 |
}
|
58 |
+
|
59 |
+
@media (max-width: 640px) {
|
60 |
+
#game-board {
|
61 |
+
width: 100% !important;
|
62 |
+
height: auto !important;
|
63 |
+
}
|
64 |
+
|
65 |
+
.game-container {
|
66 |
+
flex-direction: column !important;
|
67 |
+
}
|
68 |
+
|
69 |
+
.game-info {
|
70 |
+
width: 100% !important;
|
71 |
+
margin-bottom: 20px;
|
72 |
+
}
|
73 |
+
|
74 |
+
.game-board-container {
|
75 |
+
width: 100% !important;
|
76 |
+
}
|
77 |
+
}
|
78 |
</style>
|
79 |
</head>
|
80 |
<body class="min-h-screen flex flex-col items-center justify-center text-white overflow-hidden p-4">
|
81 |
+
<div class="game-container flex flex-col md:flex-row w-full max-w-6xl gap-8">
|
82 |
+
<div class="game-info flex flex-col items-center md:items-start md:w-1/3">
|
83 |
+
<img src="https://s3-us-west-2.amazonaws.com/anchor-generated-image-bank/staging/podcast_uploaded_nologo400/41101904/41101904-1733754532018-f9830aaa53f19.jpg"
|
84 |
+
alt="Tetris Logo"
|
85 |
+
class="w-full max-w-xs h-auto rounded-lg shadow-lg mb-4">
|
86 |
+
<div class="flex flex-col gap-4 w-full">
|
87 |
+
<div class="bg-black bg-opacity-30 p-4 rounded-lg">
|
88 |
+
<p class="text-xl">Score: <span id="score" class="font-bold">0</span></p>
|
89 |
+
</div>
|
90 |
+
<div class="bg-black bg-opacity-30 p-4 rounded-lg">
|
91 |
+
<p class="text-xl">Level: <span id="level" class="font-bold">1</span></p>
|
92 |
+
</div>
|
93 |
+
<div class="bg-black bg-opacity-30 p-4 rounded-lg">
|
94 |
+
<button id="sound-toggle" class="w-full px-4 py-2 bg-white bg-opacity-20 rounded-lg hover:bg-opacity-30 transition">
|
95 |
+
π Sound On
|
96 |
+
</button>
|
97 |
+
</div>
|
98 |
</div>
|
99 |
</div>
|
100 |
+
|
101 |
+
<div class="game-board-container md:w-2/3 flex justify-center">
|
102 |
+
<div class="relative w-full max-w-md">
|
103 |
+
<canvas id="game-board" width="300" height="600" class="w-full h-auto bg-black bg-opacity-30"></canvas>
|
104 |
+
<div id="game-over" class="hidden absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center">
|
105 |
+
<h2 class="text-3xl font-bold mb-4">GAME OVER</h2>
|
106 |
+
<p class="text-xl mb-6">Final Score: <span id="final-score" class="font-bold">0</span></p>
|
107 |
+
<button id="restart-btn" class="px-6 py-3 bg-white text-black font-bold rounded-lg hover:bg-gray-200 transition">
|
108 |
+
Play Again
|
109 |
+
</button>
|
110 |
+
</div>
|
111 |
+
<div id="start-screen" class="absolute inset-0 bg-black bg-opacity-70 flex flex-col items-center justify-center">
|
112 |
+
<button id="start-btn" class="px-8 py-4 bg-gradient-to-r from-purple-500 to-pink-500 text-white font-bold text-xl rounded-lg glow">
|
113 |
+
START GAME
|
114 |
+
</button>
|
115 |
+
<div class="mt-8 text-center">
|
116 |
+
<p class="mb-2">Controls:</p>
|
117 |
+
<p>β β : Move</p>
|
118 |
+
<p>β : Rotate</p>
|
119 |
+
<p>β : Soft Drop</p>
|
120 |
+
<p>Space : Hard Drop</p>
|
121 |
+
</div>
|
122 |
+
</div>
|
123 |
</div>
|
124 |
</div>
|
125 |
</div>
|
126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
<audio id="bg-music" loop>
|
128 |
<source src="https://music.mota.press/music/tetris/project/bgms/bgm.mp3" type="audio/mpeg">
|
129 |
</audio>
|
|
|
164 |
// Game settings
|
165 |
const COLS = 10;
|
166 |
const ROWS = 20;
|
167 |
+
const BLOCK_SIZE = 30;
|
168 |
const COLORS = [
|
169 |
null,
|
170 |
'#00d2d3', // I
|
|
|
255 |
|
256 |
// Draw the game board
|
257 |
function drawBoard() {
|
258 |
+
// Adjust canvas size for mobile
|
259 |
+
const scale = Math.min(
|
260 |
+
window.innerWidth * 0.8 / (COLS * BLOCK_SIZE),
|
261 |
+
window.innerHeight * 0.7 / (ROWS * BLOCK_SIZE)
|
262 |
+
);
|
263 |
+
|
264 |
+
const scaledWidth = COLS * BLOCK_SIZE * scale;
|
265 |
+
const scaledHeight = ROWS * BLOCK_SIZE * scale;
|
266 |
+
|
267 |
+
if (canvas.width !== scaledWidth || canvas.height !== scaledHeight) {
|
268 |
+
canvas.width = scaledWidth;
|
269 |
+
canvas.height = scaledHeight;
|
270 |
+
}
|
271 |
+
|
272 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
273 |
|
274 |
+
// Calculate block size based on current canvas dimensions
|
275 |
+
const blockWidth = canvas.width / COLS;
|
276 |
+
const blockHeight = canvas.height / ROWS;
|
277 |
+
|
278 |
// Draw existing blocks
|
279 |
board.forEach((row, y) => {
|
280 |
row.forEach((value, x) => {
|
281 |
if (value) {
|
282 |
ctx.fillStyle = COLORS[value];
|
283 |
+
ctx.fillRect(x * blockWidth, y * blockHeight, blockWidth, blockHeight);
|
284 |
|
285 |
// Add border to blocks
|
286 |
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
|
287 |
+
ctx.strokeRect(x * blockWidth, y * blockHeight, blockWidth, blockHeight);
|
288 |
}
|
289 |
});
|
290 |
});
|
|
|
296 |
if (value) {
|
297 |
ctx.fillStyle = COLORS[value];
|
298 |
ctx.fillRect(
|
299 |
+
(piece.pos.x + x) * blockWidth,
|
300 |
+
(piece.pos.y + y) * blockHeight,
|
301 |
+
blockWidth, blockHeight
|
302 |
);
|
303 |
|
304 |
// Add border to current piece
|
305 |
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
|
306 |
ctx.strokeRect(
|
307 |
+
(piece.pos.x + x) * blockWidth,
|
308 |
+
(piece.pos.y + y) * blockHeight,
|
309 |
+
blockWidth, blockHeight
|
310 |
);
|
311 |
}
|
312 |
});
|
|
|
558 |
}
|
559 |
});
|
560 |
|
561 |
+
// Touch controls for mobile
|
562 |
+
let touchStartX = 0;
|
563 |
+
let touchStartY = 0;
|
564 |
+
|
565 |
+
canvas.addEventListener('touchstart', (e) => {
|
566 |
+
touchStartX = e.touches[0].clientX;
|
567 |
+
touchStartY = e.touches[0].clientY;
|
568 |
+
e.preventDefault();
|
569 |
+
}, { passive: false });
|
570 |
+
|
571 |
+
canvas.addEventListener('touchmove', (e) => {
|
572 |
+
if (!piece || gameOver || startScreen.classList.contains('hidden') === false) return;
|
573 |
+
|
574 |
+
const touchX = e.touches[0].clientX;
|
575 |
+
const touchY = e.touches[0].clientY;
|
576 |
+
const diffX = touchX - touchStartX;
|
577 |
+
const diffY = touchY - touchStartY;
|
578 |
+
|
579 |
+
// Horizontal swipe
|
580 |
+
if (Math.abs(diffX) > 30) {
|
581 |
+
if (diffX > 0) {
|
582 |
+
movePiece(1);
|
583 |
+
} else {
|
584 |
+
movePiece(-1);
|
585 |
+
}
|
586 |
+
touchStartX = touchX;
|
587 |
+
}
|
588 |
+
|
589 |
+
// Vertical swipe down (drop)
|
590 |
+
if (diffY > 30) {
|
591 |
+
dropPiece();
|
592 |
+
touchStartY = touchY;
|
593 |
+
}
|
594 |
+
|
595 |
+
// Vertical swipe up (rotate)
|
596 |
+
if (diffY < -30) {
|
597 |
+
rotate();
|
598 |
+
touchStartY = touchY;
|
599 |
+
}
|
600 |
+
|
601 |
+
e.preventDefault();
|
602 |
+
}, { passive: false });
|
603 |
+
|
604 |
+
canvas.addEventListener('touchend', (e) => {
|
605 |
+
// Tap (hard drop)
|
606 |
+
if (Math.abs(e.changedTouches[0].clientX - touchStartX) < 10 &&
|
607 |
+
Math.abs(e.changedTouches[0].clientY - touchStartY) < 10) {
|
608 |
+
hardDrop();
|
609 |
+
}
|
610 |
+
e.preventDefault();
|
611 |
+
}, { passive: false });
|
612 |
+
|
613 |
+
// Handle window resize
|
614 |
+
window.addEventListener('resize', () => {
|
615 |
+
if (!gameOver && startScreen.classList.contains('hidden')) {
|
616 |
+
drawBoard();
|
617 |
+
}
|
618 |
+
});
|
619 |
+
|
620 |
// Initial draw
|
621 |
drawBoard();
|
622 |
});
|