tetris / index.html
bestofaiml's picture
Update index.html
91ca8e9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Classic Tetris</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #1e1e2f, #2d2d44);
color: #fff;
overflow: hidden;
}
.game-container {
display: flex;
gap: 30px;
align-items: flex-start;
}
#game-board {
border: 4px solid #4a4a6b;
border-radius: 5px;
display: grid;
grid-template-rows: repeat(20, 1fr);
grid-template-columns: repeat(10, 1fr);
gap: 1px;
background-color: #2a2a3a;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);
width: 300px;
height: 600px;
}
.cell {
border: 1px solid rgba(255, 255, 255, 0.05);
background-color: #2a2a3a;
}
.controls {
display: flex;
flex-direction: column;
gap: 30px;
}
.info-panel {
background-color: #2a2a3a;
border: 4px solid #4a4a6b;
border-radius: 5px;
padding: 20px;
width: 180px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
}
.next-piece-container {
width: 120px;
height: 120px;
display: grid;
grid-template-rows: repeat(4, 1fr);
grid-template-columns: repeat(4, 1fr);
gap: 2px;
margin-top: 10px;
margin-bottom: 20px;
}
.next-cell {
background-color: #3a3a4a;
border-radius: 2px;
}
h2 {
font-size: 18px;
margin-bottom: 10px;
color: #ddd;
}
.score-display {
font-size: 24px;
margin-bottom: 15px;
color: #fff;
}
.level-display {
font-size: 18px;
margin-bottom: 15px;
color: #fff;
}
.controls-info {
background-color: #2a2a3a;
border: 4px solid #4a4a6b;
border-radius: 5px;
padding: 20px;
width: 180px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
}
.control-item {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
font-size: 14px;
color: #ccc;
}
.key {
background-color: #4a4a6b;
color: #fff;
padding: 3px 8px;
border-radius: 4px;
font-family: monospace;
}
.game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.game-over.show {
opacity: 1;
pointer-events: all;
}
.game-over h1 {
font-size: 42px;
margin-bottom: 20px;
color: #ff5555;
}
.final-score {
font-size: 24px;
margin-bottom: 30px;
}
.restart-btn {
background-color: #4CAF50;
color: white;
border: none;
padding: 12px 24px;
font-size: 16px;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.3s;
}
.restart-btn:hover {
background-color: #45a049;
}
.tetromino-i { background-color: #00f0f0; }
.tetromino-j { background-color: #0000f0; }
.tetromino-l { background-color: #f0a000; }
.tetromino-o { background-color: #f0f000; }
.tetromino-s { background-color: #00f000; }
.tetromino-t { background-color: #a000f0; }
.tetromino-z { background-color: #f00000; }
.tetromino-i.ghost { background-color: rgba(0, 240, 240, 0.2); }
.tetromino-j.ghost { background-color: rgba(0, 0, 240, 0.2); }
.tetromino-l.ghost { background-color: rgba(240, 160, 0, 0.2); }
.tetromino-o.ghost { background-color: rgba(240, 240, 0, 0.2); }
.tetromino-s.ghost { background-color: rgba(0, 240, 0, 0.2); }
.tetromino-t.ghost { background-color: rgba(160, 0, 240, 0.2); }
.tetromino-z.ghost { background-color: rgba(240, 0, 0, 0.2); }
@media (max-width: 768px) {
.game-container {
flex-direction: column;
align-items: center;
}
.controls {
flex-direction: row;
margin-top: 20px;
}
}
</style>
</head>
<body>
<div class="game-container">
<div id="game-board"></div>
<div class="controls">
<div class="info-panel">
<h2>Next Piece</h2>
<div class="next-piece-container" id="next-piece"></div>
<h2>Score</h2>
<div class="score-display" id="score">0</div>
<h2>Level</h2>
<div class="level-display" id="level">1</div>
</div>
<div class="controls-info">
<h2>Controls</h2>
<div class="control-item">
<span>Move Left</span>
<span class="key"></span>
</div>
<div class="control-item">
<span>Move Right</span>
<span class="key"></span>
</div>
<div class="control-item">
<span>Rotate</span>
<span class="key"></span>
</div>
<div class="control-item">
<span>Soft Drop</span>
<span class="key"></span>
</div>
<div class="control-item">
<span>Hard Drop</span>
<span class="key">Space</span>
</div>
<div class="control-item">
<span>Pause</span>
<span class="key">P</span>
</div>
</div>
</div>
</div>
<div class="game-over" id="game-over">
<h1>GAME OVER</h1>
<div class="final-score">Score: <span id="final-score">0</span></div>
<button class="restart-btn" id="restart-btn">Play Again</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Game constants
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 30;
const NEXT_PIECE_COLS = 4;
const NEXT_PIECE_ROWS = 4;
// DOM elements
const gameBoard = document.getElementById('game-board');
const nextPieceContainer = document.getElementById('next-piece');
const scoreDisplay = document.getElementById('score');
const levelDisplay = document.getElementById('level');
const gameOverScreen = document.getElementById('game-over');
const finalScoreDisplay = document.getElementById('final-score');
const restartBtn = document.getElementById('restart-btn');
// Game state
let board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
let currentPiece = null;
let nextPiece = null;
let currentPosition = { x: 0, y: 0 };
let score = 0;
let level = 1;
let linesCleared = 0;
let gameOver = false;
let isPaused = false;
let dropStart;
let gameInterval;
// Tetromino shapes
const SHAPES = {
I: [
[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]
],
J: [
[1, 0, 0],
[1, 1, 1],
[0, 0, 0]
],
L: [
[0, 0, 1],
[1, 1, 1],
[0, 0, 0]
],
O: [
[1, 1],
[1, 1]
],
S: [
[0, 1, 1],
[1, 1, 0],
[0, 0, 0]
],
T: [
[0, 1, 0],
[1, 1, 1],
[0, 0, 0]
],
Z: [
[1, 1, 0],
[0, 1, 1],
[0, 0, 0]
]
};
const COLORS = {
I: 'tetromino-i',
J: 'tetromino-j',
L: 'tetromino-l',
O: 'tetromino-o',
S: 'tetromino-s',
T: 'tetromino-t',
Z: 'tetromino-z'
};
// Initialize game board
function initBoard() {
gameBoard.innerHTML = '';
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.id = `${row}-${col}`;
gameBoard.appendChild(cell);
}
}
}
// Initialize next piece display
function initNextPieceDisplay() {
nextPieceContainer.innerHTML = '';
for (let row = 0; row < NEXT_PIECE_ROWS; row++) {
for (let col = 0; col < NEXT_PIECE_COLS; col++) {
const cell = document.createElement('div');
cell.className = 'next-cell';
cell.id = `next-${row}-${col}`;
nextPieceContainer.appendChild(cell);
}
}
}
// Get random tetromino
function getRandomPiece() {
const keys = Object.keys(SHAPES);
const randomKey = keys[Math.floor(Math.random() * keys.length)];
return {
shape: SHAPES[randomKey],
color: COLORS[randomKey],
type: randomKey
};
}
// Draw the current piece on the board
function drawPiece(x, y, piece, isGhost = false) {
piece.shape.forEach((row, rowIndex) => {
row.forEach((value, colIndex) => {
if (value) {
const boardRow = y + rowIndex;
const boardCol = x + colIndex;
if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) {
const cell = document.getElementById(`${boardRow}-${boardCol}`);
if (cell) {
cell.classList.add(piece.color);
if (isGhost) {
cell.classList.add('ghost');
}
}
}
}
});
});
}
// Clear the current piece from the board
function clearPiece(x, y, piece) {
piece.shape.forEach((row, rowIndex) => {
row.forEach((value, colIndex) => {
if (value) {
const boardRow = y + rowIndex;
const boardCol = x + colIndex;
if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) {
const cell = document.getElementById(`${boardRow}-${boardCol}`);
if (cell) {
cell.className = 'cell';
}
}
}
});
});
}
// Draw the ghost piece (projection of where the piece will land)
function drawGhostPiece() {
let ghostY = currentPosition.y;
while (!collision(currentPosition.x, ghostY + 1, currentPiece)) {
ghostY++;
}
if (ghostY !== currentPosition.y) {
// First clear any existing ghost pieces
clearGhost();
// Draw the new ghost piece
drawPiece(currentPosition.x, ghostY, currentPiece, true);
}
}
// Clear all ghost pieces from the board
function clearGhost() {
const ghostCells = document.querySelectorAll('.ghost');
ghostCells.forEach(cell => {
const className = cell.className;
const colorClass = className.split(' ').find(cls => cls.startsWith('tetromino-'));
cell.className = 'cell';
if (colorClass) {
cell.classList.remove(colorClass, 'ghost');
}
});
}
// Check for collision
function collision(x, y, piece) {
for (let row = 0; row < piece.shape.length; row++) {
for (let col = 0; col < piece.shape[row].length; col++) {
if (!piece.shape[row][col]) continue;
const boardX = x + col;
const boardY = y + row;
if (
boardX < 0 ||
boardX >= COLS ||
boardY >= ROWS ||
(boardY >= 0 && board[boardY][boardX])
) {
return true;
}
}
}
return false;
}
// Rotate piece
function rotate(piece) {
const N = piece.shape.length;
const rotated = Array(N).fill().map(() => Array(N).fill(0));
// Transpose the matrix
for (let i = 0; i < N; i++) {
for (let j = 0; j < N; j++) {
rotated[i][j] = piece.shape[N - j - 1][i];
}
}
// Special case for I piece to make it rotate properly
if (piece.type === 'I') {
if (currentPosition.x < 0) {
currentPosition.x = 0;
} else if (currentPosition.x > COLS - 4) {
currentPosition.x = COLS - 4;
}
}
return {
...piece,
shape: rotated
};
}
// Lock piece in place
function lockPiece() {
currentPiece.shape.forEach((row, rowIndex) => {
row.forEach((value, colIndex) => {
if (value) {
const boardRow = currentPosition.y + rowIndex;
const boardCol = currentPosition.x + colIndex;
if (boardRow >= 0) {
board[boardRow][boardCol] = currentPiece.color;
}
}
});
});
// Check for completed lines
checkLines();
// Check for game over
if (currentPosition.y <= 0) {
gameOver = true;
showGameOver();
return;
}
// Get next piece
currentPiece = nextPiece;
nextPiece = getRandomPiece();
currentPosition = { x: Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2), y: 0 };
// Update next piece display
updateNextPieceDisplay();
// Draw the new piece and ghost
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
drawGhostPiece();
// Reset drop interval
dropStart = Date.now();
}
// Check for completed lines
function checkLines() {
let linesToClear = 0;
for (let row = ROWS - 1; row >= 0; row--) {
if (board[row].every(cell => cell)) {
linesToClear++;
// Shift all rows above down
for (let y = row; y > 0; y--) {
board[y] = [...board[y - 1]];
}
board[0] = Array(COLS).fill(0);
// Since we modified the current row, need to check it again
row++;
}
}
if (linesToClear > 0) {
// Update score
updateScore(linesToClear);
// Redraw the board
drawBoard();
}
}
// Update score
function updateScore(lines) {
const points = [0, 40, 100, 300, 1200]; // Points for 0, 1, 2, 3, 4 lines
score += points[lines] * level;
linesCleared += lines;
// Every 10 lines increases the level
level = Math.floor(linesCleared / 10) + 1;
// Update displays
scoreDisplay.textContent = score;
levelDisplay.textContent = level;
}
// Draw the entire board
function drawBoard() {
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < COLS; col++) {
const cell = document.getElementById(`${row}-${col}`);
cell.className = 'cell';
if (board[row][col]) {
cell.classList.add(board[row][col]);
}
}
}
}
// Update next piece display
function updateNextPieceDisplay() {
// Clear the next piece display
for (let row = 0; row < NEXT_PIECE_ROWS; row++) {
for (let col = 0; col < NEXT_PIECE_COLS; col++) {
const cell = document.getElementById(`next-${row}-${col}`);
cell.className = 'next-cell';
}
}
// Draw the next piece in the center
const startRow = Math.floor((NEXT_PIECE_ROWS - nextPiece.shape.length) / 2);
const startCol = Math.floor((NEXT_PIECE_COLS - nextPiece.shape[0].length) / 2);
for (let row = 0; row < nextPiece.shape.length; row++) {
for (let col = 0; col < nextPiece.shape[row].length; col++) {
if (nextPiece.shape[row][col]) {
const cell = document.getElementById(`next-${startRow + row}-${startCol + col}`);
if (cell) {
cell.classList.add(nextPiece.color);
}
}
}
}
}
// Move piece down
function moveDown() {
if (gameOver || isPaused) return;
clearPiece(currentPosition.x, currentPosition.y, currentPiece);
clearGhost();
if (!collision(currentPosition.x, currentPosition.y + 1, currentPiece)) {
currentPosition.y++;
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
drawGhostPiece();
return true;
} else {
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
drawGhostPiece();
lockPiece();
return false;
}
}
// Hard drop
function hardDrop() {
if (gameOver || isPaused) return;
clearPiece(currentPosition.x, currentPosition.y, currentPiece);
clearGhost();
while (!collision(currentPosition.x, currentPosition.y + 1, currentPiece)) {
currentPosition.y++;
}
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
lockPiece();
dropStart = Date.now();
}
// Move piece left or right
function movePiece(direction) {
if (gameOver || isPaused) return;
clearPiece(currentPosition.x, currentPosition.y, currentPiece);
clearGhost();
const newX = currentPosition.x + direction;
if (!collision(newX, currentPosition.y, currentPiece)) {
currentPosition.x = newX;
}
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
drawGhostPiece();
}
// Rotate current piece
function rotatePiece() {
if (gameOver || isPaused) return;
clearPiece(currentPosition.x, currentPosition.y, currentPiece);
clearGhost();
const rotated = rotate(currentPiece);
// Wall kick - try moving left/right if rotation would cause a collision
if (!collision(currentPosition.x, currentPosition.y, rotated)) {
currentPiece = rotated;
} else if (!collision(currentPosition.x - 1, currentPosition.y, rotated)) {
currentPiece = rotated;
currentPosition.x--;
} else if (!collision(currentPosition.x + 1, currentPosition.y, rotated)) {
currentPiece = rotated;
currentPosition.x++;
}
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
drawGhostPiece();
}
// Game loop
function gameLoop() {
const now = Date.now();
const delta = now - dropStart;
const dropInterval = Math.max(1000 - (level - 1) * 100, 100); // Speeds up as level increases
if (delta > dropInterval) {
moveDown();
dropStart = now;
}
if (!gameOver) {
requestAnimationFrame(gameLoop);
}
}
// Show game over screen
function showGameOver() {
gameOverScreen.classList.add('show');
finalScoreDisplay.textContent = score;
clearInterval(gameInterval);
}
// Reset game
function resetGame() {
board = Array(ROWS).fill().map(() => Array(COLS).fill(0));
score = 0;
level = 1;
linesCleared = 0;
gameOver = false;
isPaused = false;
scoreDisplay.textContent = score;
levelDisplay.textContent = level;
// Initialize pieces
currentPiece = getRandomPiece();
nextPiece = getRandomPiece();
currentPosition = { x: Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2), y: 0 };
// Clear the board
drawBoard();
updateNextPieceDisplay();
// Draw current piece and ghost
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
drawGhostPiece();
// Hide game over screen
gameOverScreen.classList.remove('show');
// Reset drop time and start game loop
dropStart = Date.now();
gameLoop();
}
// Pause/unpause the game
function togglePause() {
if (gameOver) return;
isPaused = !isPaused;
if (!isPaused) {
dropStart = Date.now(); // Reset drop timer
gameLoop();
}
}
// Initialize the game
function init() {
initBoard();
initNextPieceDisplay();
// Initialize first pieces
currentPiece = getRandomPiece();
nextPiece = getRandomPiece();
currentPosition = { x: Math.floor(COLS / 2) - Math.floor(currentPiece.shape[0].length / 2), y: 0 };
updateNextPieceDisplay();
drawPiece(currentPosition.x, currentPosition.y, currentPiece);
drawGhostPiece();
// Keyboard controls
document.addEventListener('keydown', event => {
if (event.key === 'ArrowLeft') {
movePiece(-1);
} else if (event.key === 'ArrowRight') {
movePiece(1);
} else if (event.key === 'ArrowDown') {
moveDown();
} else if (event.key === 'ArrowUp') {
rotatePiece();
} else if (event.key === ' ') {
hardDrop();
} else if (event.key === 'p' || event.key === 'P') {
togglePause();
}
// Prevent default for arrow keys and space to avoid scrolling
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', ' '].includes(event.key)) {
event.preventDefault();
}
});
restartBtn.addEventListener('click', resetGame);
// Start game loop
dropStart = Date.now();
gameLoop();
}
init();
});
</script>
</body>
</html>