Tetris Eval
Prompt
build a modern Tetris game, with the shadow placement thing. build it in HTML.
A system prompt was added to support web rendering
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Modern Tetris</title> <style> html,body{ height:100%; margin:0; display:flex; align-items:center; justify-content:center; background:#111; color:#fff; font-family:Arial,Helvetica,sans-serif; user-select:none; } #gameWrapper{ position:relative; display:flex; } canvas{ background:#000; box-shadow:0 0 10px #0f0, 0 0 40px #0f0 inset; } #score{ margin-left:20px; font-size:18px; line-height:1.4; } #score h2{ margin:0 0 8px 0; font-size:24px; } #about{ font-size:12px; margin-top:10px; } </style> </head> <body> <div id="gameWrapper"> <canvas id="tetris" width="300" height="600"></canvas> <div id="score"> <h2>Score</h2> <div id="scoreVal">0</div> <h2>Lines</h2> <div id="linesVal">0</div> <h2>Level</h2> <div id="levelVal">1</div> <div id="about"> Controls:<br> ← / → Move<br> ↑ Rotate<br> ↓ Soft-drop<br> Space Hard-drop<br> P Pause </div> </div> </div> <script> (() => { /* --- Canvas & constants ------------------------------------------------- */ const canvas = document.getElementById('tetris'); const context = canvas.getContext('2d'); context.scale(30,30); // each block = 30×30 px const COLS = 10, ROWS = 20; const colors = [ null, '#00f0f0', // I '#0000f0', // J '#f0a000', // L '#f0f000', // O '#00f000', // S '#a000f0', // T '#f00000' // Z ]; /* --- Board helpers ------------------------------------------------------ */ function createMatrix(w,h){ const matrix=[]; while(h--) matrix.push(new Array(w).fill(0)); return matrix; } const board = createMatrix(COLS,ROWS); function collide(board,player){ const [m,o] = [player.matrix,player.pos]; for(let y=0;y<m.length;++y){ for(let x=0;x<m[y].length;++x){ if(m[y][x]!==0 && (board[y+o.y] && board[y+o.y][x+o.x]) !== 0){ return true; } } } return false; } function merge(board,player){ player.matrix.forEach((row,y)=>{ row.forEach((val,x)=>{ if(val!==0) board[y+player.pos.y][x+player.pos.x]=val; }); }); } function sweep(){ let rowCount=0; outer: for(let y=board.length-1;y>=0;--y){ for(let x=0;x<board[y].length;++x){ if(board[y][x]===0) continue outer; } const row = board.splice(y,1)[0].fill(0); board.unshift(row); ++y; rowCount++; } if(rowCount){ player.score += rowCount*10*rowCount; player.lines += rowCount; if(player.lines >= player.level*10){ player.level++; dropInterval = Math.max(100, dropInterval-100); } updateHUD(); } } /* --- Piece generation & rotation --------------------------------------- */ function createPiece(type){ switch(type){ case 'T': return [ [0,0,0], [1,1,1], [0,1,0]]; case 'O': return [ [2,2], [2,2]]; case 'L': return [ [0,0,3], [3,3,3], [0,0,0]]; case 'J': return [ [4,0,0], [4,4,4], [0,0,0]]; case 'I': return [ [0,0,0,0], [5,5,5,5], [0,0,0,0], [0,0,0,0]]; case 'S': return [ [0,6,6], [6,6,0], [0,0,0]]; case 'Z': return [ [7,7,0], [0,7,7], [0,0,0]]; } } function rotate(mat,dir){ for(let y=0;y<mat.length;++y){ for(let x=0;x<y;++x){ [mat[x][y],mat[y][x]] = [mat[y][x],mat[x][y]]; } } dir>0 ? mat.forEach(r=>r.reverse()) : mat.reverse(); } function playerRotate(dir){ const pos=player.pos.x; let offset=1; rotate(player.matrix,dir); while(collide(board,player)){ player.pos.x += offset; offset = -(offset + (offset>0?1:-1)); if(offset>player.matrix[0].length){ rotate(player.matrix,-dir); player.pos.x = pos; return; } } } /* --- Render ------------------------------------------------------------- */ function drawMatrix(matrix,offset,alpha=1){ context.globalAlpha = alpha; matrix.forEach((row,y)=>{ row.forEach((val,x)=>{ if(val!==0){ context.fillStyle = colors[val]; context.fillRect(x+offset.x, y+offset.y, 1,1); context.strokeStyle = '#000'; context.lineWidth = 0.05; context.strokeRect(x+offset.x, y+offset.y, 1,1); } }); }); context.globalAlpha = 1; } function draw(){ context.fillStyle = '#000'; context.fillRect(0,0,COLS,ROWS); drawMatrix(board,{x:0,y:0}); /* ghost (shadow) piece ------------------------------------------------- */ const ghost = {pos:{x:player.pos.x,y:player.pos.y}, matrix:player.matrix}; while(!collide(board,{pos:{x:ghost.pos.x,y:ghost.pos.y+1},matrix:ghost.matrix})){ ghost.pos.y++; } drawMatrix(ghost.matrix,ghost.pos,0.25); drawMatrix(player.matrix,player.pos); } /* --- Game loop ---------------------------------------------------------- */ let dropCounter=0, dropInterval=1000, lastTime=0, paused=false; function update(time=0){ if(paused){ requestAnimationFrame(update); return; } const delta = time-lastTime; lastTime=time; dropCounter += delta; if(dropCounter > dropInterval) playerDrop(); draw(); requestAnimationFrame(update); } /* --- Player actions ----------------------------------------------------- */ function playerMove(dir){ player.pos.x += dir; if(collide(board,player)) player.pos.x -= dir; } function playerDrop(){ player.pos.y++; if(collide(board,player)){ player.pos.y--; merge(board,player); resetPlayer(); sweep(); } dropCounter = 0; } function hardDrop(){ while(!collide(board,player)) player.pos.y++; player.pos.y--; merge(board,player); resetPlayer(); sweep(); dropCounter = 0; } /* --- Init / reset ------------------------------------------------------- */ const player={ pos:{x:0,y:0}, matrix:null, score:0, lines:0, level:1 }; function resetPlayer(){ const pieces='TJLOSZI'; player.matrix = createPiece(pieces[Math.random()*pieces.length|0]); player.pos.y = 0; player.pos.x = (COLS>>1) - (player.matrix[0].length>>1); if(collide(board,player)){ // Game over board.forEach(r=>r.fill(0)); player.score=player.lines=0; player.level=1; dropInterval=1000; updateHUD(); alert('Game Over'); } } /* --- HUD ---------------------------------------------------------------- */ function updateHUD(){ scoreVal.textContent = player.score; linesVal.textContent = player.lines; levelVal.textContent = player.level; } const scoreVal = document.getElementById('scoreVal'); const linesVal = document.getElementById('linesVal'); const levelVal = document.getElementById('levelVal'); /* --- Controls ----------------------------------------------------------- */ document.addEventListener('keydown',e=>{ switch(e.code){ case 'ArrowLeft': playerMove(-1); break; case 'ArrowRight':playerMove( 1); break; case 'ArrowDown': playerDrop(); break; case 'ArrowUp': playerRotate(1);break; case 'Space': hardDrop(); break; case 'KeyP': paused=!paused;break; } }); /* --- Start -------------------------------------------------------------- */ resetPlayer(); updateHUD(); update(); })(); </script> </body> </html> ```
Drag to resize
Of course. Here is a complete, self-contained HTML file for a modern Tetris game, including the ghost piece feature. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Modern Tetris</title> <style> :root { --bg-color: #121212; --grid-color: #333; --text-color: #f0f0f0; --accent-color: #00bcd4; --border-color: #444; --game-over-bg: rgba(0, 0, 0, 0.75); } @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap'); body { background-color: var(--bg-color); color: var(--text-color); font-family: 'Poppins', sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; text-align: center; } h1 { color: var(--accent-color); text-transform: uppercase; letter-spacing: 3px; margin-bottom: 0.5em; } .game-container { display: flex; gap: 20px; align-items: flex-start; border: 2px solid var(--border-color); padding: 20px; border-radius: 12px; background-color: #1e1e1e; box-shadow: 0 10px 30px rgba(0,0,0,0.5); } .game-board-container { position: relative; } canvas#gameCanvas { border: 2px solid var(--border-color); background-color: var(--bg-color); border-radius: 8px; } .game-info { display: flex; flex-direction: column; gap: 15px; width: 180px; font-size: 1.1em; } .info-box { background-color: var(--bg-color); padding: 15px; border-radius: 8px; border: 1px solid var(--border-color); } .info-box h2 { margin: 0 0 10px 0; font-size: 1em; color: var(--accent-color); border-bottom: 1px solid var(--border-color); padding-bottom: 5px; } .info-box p { margin: 0; font-size: 1.2em; font-weight: 600; } #next-piece-canvas { background-color: var(--bg-color); border-radius: 4px; } .controls { margin-top: 10px; font-size: 0.9em; line-height: 1.6; color: #aaa; } .controls strong { color: #ddd; } #game-over-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: var(--game-over-bg); color: white; display: none; /* Initially hidden */ flex-direction: column; justify-content: center; align-items: center; font-size: 2em; border-radius: 8px; } #game-over-overlay button { margin-top: 20px; padding: 10px 20px; font-size: 0.6em; font-family: 'Poppins', sans-serif; background-color: var(--accent-color); border: none; color: black; border-radius: 5px; cursor: pointer; text-transform: uppercase; font-weight: 600; transition: background-color 0.2s ease; } #game-over-overlay button:hover { background-color: #00efff; } </style> </head> <body> <div class="game-wrapper"> <h1>Tetris</h1> <div class="game-container"> <div class="game-board-container"> <canvas id="gameCanvas" width="300" height="600"></canvas> <div id="game-over-overlay"> <h2>Game Over</h2> <button id="restart-button">Play Again</button> </div> </div> <aside class="game-info"> <div class="info-box"> <h2>SCORE</h2> <p id="score">0</p> </div> <div class="info-box"> <h2>LINES</h2> <p id="lines">0</p> </div> <div class="info-box"> <h2>LEVEL</h2> <p id="level">1</p> </div> <div class="info-box"> <h2>NEXT</h2> <canvas id="next-piece-canvas" width="120" height="120"></canvas> </div> <div class="controls info-box"> <h2>CONTROLS</h2> <p style="font-size:0.8em; font-weight: 400; line-height: 1.8;"> <strong>Left/Right</strong>: Move<br> <strong>Down</strong>: Soft Drop<br> <strong>Up / X</strong>: Rotate<br> <strong>Space</strong>: Hard Drop<br> <strong>C</strong>: Hold Piece </p> </div> </aside> </div> </div> <script> // --- Canvas and Context --- const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const nextCanvas = document.getElementById('next-piece-canvas'); const nextCtx = nextCanvas.getContext('2d'); // --- UI Elements --- const scoreElement = document.getElementById('score'); const linesElement = document.getElementById('lines'); const levelElement = document.getElementById('level'); const gameOverOverlay = document.getElementById('game-over-overlay'); const restartButton = document.getElementById('restart-button'); // --- Game Constants --- const COLS = 10; const ROWS = 20; const BLOCK_SIZE = 30; const COLORS = [ null, // 0: Empty '#00bcd4', // 1: I (Cyan) '#fdd835', // 2: O (Yellow) '#ab47bc', // 3: T (Purple) '#ff9800', // 4: L (Orange) '#2196f3', // 5: J (Blue) '#4caf50', // 6: S (Green) '#f44336' // 7: Z (Red) ]; const PIECES = { 'I': [[1, 1, 1, 1]], 'O': [[2, 2], [2, 2]], 'T': [[0, 3, 0], [3, 3, 3]], 'L': [[0, 0, 4], [4, 4, 4]], 'J': [[5, 0, 0], [5, 5, 5]], 'S': [[0, 6, 6], [6, 6, 0]], 'Z': [[7, 7, 0], [0, 7, 7]] }; const PIECE_KEYS = Object.keys(PIECES); const POINTS = { 1: 40, 2: 100, 3: 300, 4: 1200 }; // --- Game State --- let board; let player; let nextPiece; let score; let lines; let level; let dropCounter; let dropInterval; let lastTime; let animationFrameId; let isGameOver; // --- Player Object Blueprint --- function createPlayer(type) { if (!type) { type = PIECE_KEYS[PIECE_KEYS.length * Math.random() | 0]; } const matrix = PIECES[type]; const colorIndex = matrix.flat().find(v => v > 0); return { x: Math.floor(COLS / 2) - Math.floor(matrix[0].length / 2), y: 0, matrix: matrix, color: COLORS[colorIndex] }; } // --- Game Logic --- function init() { // Initialize board board = Array.from({ length: ROWS }, () => Array(COLS).fill(0)); // Reset game state score = 0; lines = 0; level = 1; isGameOver = false; // Create first pieces player = createPlayer(); nextPiece = createPlayer(); // Timing dropCounter = 0; dropInterval = 1000; lastTime = 0; // Hide game over screen gameOverOverlay.style.display = 'none'; // Start game loop update(); } function resetPlayer() { player = nextPiece; nextPiece = createPlayer(); player.x = Math.floor(COLS / 2) - Math.floor(player.matrix[0].length / 2); player.y = 0; if (checkCollision(player, board)) { isGameOver = true; gameOver(); } } function playerDrop() { player.y++; if (checkCollision(player, board)) { player.y--; lockPiece(); clearLines(); resetPlayer(); } dropCounter = 0; } function playerMove(dir) { if (isGameOver) return; player.x += dir; if (checkCollision(player, board)) { player.x -= dir; } } function playerRotate() { if (isGameOver) return; const originalMatrix = player.matrix; const rotated = rotate(player.matrix); player.matrix = rotated; // Wall kick logic const pos = player.x; let offset = 1; while (checkCollision(player, board)) { player.x += offset; offset = -(offset + (offset > 0 ? 1 : -1)); if (offset > player.matrix[0].length) { player.matrix = originalMatrix; // Can't rotate, revert player.x = pos; return; } } } function rotate(matrix) { const rows = matrix.length; const cols = matrix[0].length; const newMatrix = Array.from({ length: cols }, () => Array(rows).fill(0)); for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { newMatrix[c][rows - 1 - r] = matrix[r][c]; } } return newMatrix; } function hardDrop() { if (isGameOver) return; while (!checkCollision(player, board)) { player.y++; } player.y--; lockPiece(); clearLines(); resetPlayer(); } function checkCollision(playerPiece, gameBoard) { const { matrix, x, y } = playerPiece; for (let row = 0; row < matrix.length; row++) { for (let col = 0; col < matrix[row].length; col++) { if (matrix[row][col] !== 0) { let newX = col + x; let newY = row + y; if (newX < 0 || newX >= COLS || newY >= ROWS || (gameBoard[newY] && gameBoard[newY][newX] !== 0)) { return true; } } } } return false; } function lockPiece() { const { matrix, x, y } = player; const colorIndex = matrix.flat().find(v => v > 0); matrix.forEach((row, r) => { row.forEach((value, c) => { if (value !== 0) { board[r + y][c + x] = colorIndex; } }); }); } function clearLines() { let clearedLines = 0; outer: for (let r = ROWS - 1; r >= 0; r--) { for (let c = 0; c < COLS; c++) { if (board[r][c] === 0) { continue outer; } } const row = board.splice(r, 1)[0].fill(0); board.unshift(row); clearedLines++; r++; // re-check the same row index since we shifted everything down } if (clearedLines > 0) { score += POINTS[clearedLines] * level; lines += clearedLines; level = Math.floor(lines / 10) + 1; dropInterval = 1000 - (level - 1) * 50; if (dropInterval < 100) dropInterval = 100; } } function update(time = 0) { if (isGameOver) { return; } const deltaTime = time - lastTime; lastTime = time; dropCounter += deltaTime; if (dropCounter > dropInterval) { playerDrop(); } draw(); updateUI(); animationFrameId = requestAnimationFrame(update); } function gameOver() { cancelAnimationFrame(animationFrameId); gameOverOverlay.style.display = 'flex'; } // --- Drawing Functions --- function draw() { // Clear main canvas ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#121212'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw grid drawGrid(); // Draw placed pieces drawMatrix(board, { x: 0, y: 0 }, ctx); // Draw ghost piece drawGhostPiece(); // Draw current player piece drawMatrix(player.matrix, player, ctx, player.color); } function drawGrid() { ctx.strokeStyle = '#333'; ctx.lineWidth = 1; for (let x = 0; x < COLS; x++) { ctx.beginPath(); ctx.moveTo(x * BLOCK_SIZE, 0); ctx.lineTo(x * BLOCK_SIZE, ROWS * BLOCK_SIZE); ctx.stroke(); } for (let y = 0; y < ROWS; y++) { ctx.beginPath(); ctx.moveTo(0, y * BLOCK_SIZE); ctx.lineTo(COLS * BLOCK_SIZE, y * BLOCK_SIZE); ctx.stroke(); } } function drawMatrix(matrix, offset, context, overrideColor) { matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { const color = overrideColor || COLORS[value]; context.fillStyle = color; context.fillRect((x + offset.x) * BLOCK_SIZE, (y + offset.y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); // Add a subtle 3D effect context.fillStyle = 'rgba(255, 255, 255, 0.15)'; context.fillRect((x + offset.x) * BLOCK_SIZE, (y + offset.y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE / 8); context.fillStyle = 'rgba(0, 0, 0, 0.25)'; context.fillRect((x + offset.x) * BLOCK_SIZE, (y + offset.y + (7/8)) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE / 8); } }); }); } function drawGhostPiece() { const ghost = JSON.parse(JSON.stringify(player)); // Deep copy while (!checkCollision(ghost, board)) { ghost.y++; } ghost.y--; const ghostColor = 'rgba(255, 255, 255, 0.2)'; ghost.matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { ctx.fillStyle = ghostColor; ctx.fillRect((x + ghost.x) * BLOCK_SIZE, (y + ghost.y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); } }); }); } function drawNextPiece() { nextCtx.clearRect(0, 0, nextCanvas.width, nextCanvas.height); nextCtx.fillStyle = '#121212'; nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height); const piece = nextPiece; const scale = 25; // Block size for next canvas const offsetX = (nextCanvas.width - piece.matrix[0].length * scale) / 2; const offsetY = (nextCanvas.height - piece.matrix.length * scale) / 2; piece.matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { nextCtx.fillStyle = piece.color; nextCtx.fillRect(offsetX + x * scale, offsetY + y * scale, scale, scale); } }) }); } function updateUI() { scoreElement.innerText = score; linesElement.innerText = lines; levelElement.innerText = level; drawNextPiece(); } // --- Controls --- document.addEventListener('keydown', event => { if (isGameOver) return; switch (event.code) { case 'ArrowLeft': playerMove(-1); break; case 'ArrowRight': playerMove(1); break; case 'ArrowDown': playerDrop(); break; case 'ArrowUp': case 'KeyX': playerRotate(); break; case 'Space': event.preventDefault(); // Prevent page scrolling hardDrop(); break; } }); restartButton.addEventListener('click', () => { init(); }); // --- Start the game --- init(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Modern Tetris</title> <style> * { box-sizing: border-box; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #111; color: #fafafa; display: flex; justify-content: center; align-items: flex-start; padding-top: 40px; } #wrapper { display: flex; gap: 40px; } #board { display: grid; grid-template-rows: repeat(20, 28px); grid-template-columns: repeat(10, 28px); gap: 1px; background: #000; border: 2px solid #333; margin-right: 10px; } #board .tile { width: 28px; height: 28px; } #sidebar { width: 160px; } #score, #level, #lines { font-size: 20px; margin-bottom: 20px; } #controls ul { margin: 0; padding-left: 20px; font-size: 14px; color: #aaa; } </style> </head> <body> <div id="wrapper"> <div id="board"></div> <div id="sidebar"> <div id="score">Score 0</div> <div id="level">Level 0</div> <div id="lines">Lines 0</div> <div id="controls"> <ul> <li>←/→ : Move</li> <li>↓ : Soft drop</li> <li>↑ : Rotate</li> <li>Space : Hard drop</li> </ul> </div> </div> </div> <script> /* --- CONFIG --- */ const COLS = 10; const ROWS = 20; const EMPTY = 0; const COLORS = [ "#111", // 0 "#00fff7", // I "#007fff", // J "#ff7f00", // L "#ffff00", // O "#00ff00", // S "#8f00ff", // T "#ff0000" // Z ]; const SHAPES = [ [], [[0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0]], // I [[2,0,0], [2,2,2], [0,0,0]], // J [[0,0,3], [3,3,3], [0,0,0]], // L [[4,4], [4,4]], // O [[0,5,5], [5,5,0], [0,0,0]], // S [[0,6,0], [6,6,6], [0,0,0]], // T [[7,7,0], [0,7,7], [0,0,0]] // Z ]; /* --- DOM --- */ const boardEl = document.getElementById("board"); for (let r = 0; r < ROWS; r++) { for (let c = 0; c < COLS; c++) { const tile = document.createElement("div"); tile.className = "tile"; tile.style.backgroundColor = COLORS[0]; boardEl.appendChild(tile); } } const tiles = [...boardEl.children]; function drawTile(r, c, colorIndex) { if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return; tiles[r * COLS + c].style.backgroundColor = COLORS[colorIndex]; } /* --- BOARD LOGIC --- */ let board = Array.from({ length: ROWS }, () => Array(COLS).fill(EMPTY)); function draw() { tiles.forEach((tile, i) => { const r = Math.floor(i / COLS); const c = i % COLS; tile.style.backgroundColor = COLORS[board[r][c]]; }); drawPiece(piece); drawGhost(); } function drawPiece(p) { for (let r = 0; r < p.matrix.length; r++) { for (let c = 0; c < p.matrix[r].length; c++) { if (p.matrix[r][c] !== 0) { drawTile(p.y + r, p.x + c, p.matrix[r][c]); } } } } function drawGhost() { const ghost = getGhostPosition(); for (let r = 0; r < ghost.matrix.length; r++) { for (let c = 0; c < ghost.matrix[r].length; c++) { if (ghost.matrix[r][c] !== 0) { const row = ghost.y + r; const col = ghost.x + c; if (row !== piece.y + r || col !== piece.x + c) { if (board[row][col] === EMPTY) { tiles[row * COLS + col].style.backgroundColor = COLORS[ghost.matrix[r][c]] + "55"; } } } } } } function getGhostPosition() { const ghost = { ...piece, matrix: [...piece.matrix] }; while (!isCollision(ghost.matrix, ghost.x, ghost.y + 1)) { ghost.y++; } return ghost; } /* --- PIECE LOGIC --- */ let piece = null; function createPiece(type) { const shape = SHAPES[type]; return { matrix: JSON.parse(JSON.stringify(shape)), x: Math.floor((COLS - shape[0].length) / 2), y: 0 }; } function rotate(matrix) { const rows = matrix.length; const cols = matrix[0].length; const result = Array.from({ length: cols }, () => Array(rows).fill(0)); for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { result[c][r] = matrix[r][c]; } } return result.map(row => row.reverse()); } function isCollision(matrix, offsetX = 0, offsetY = 0) { for (let r = 0; r < matrix.length; r++) { for (let c = 0; c < matrix[r].length; c++) { if (matrix[r][c] === 0) continue; const x = offsetX + c; const y = offsetY + r; if ( x < 0 || x >= COLS || y >= ROWS || (y >= 0 && board[y][x] !== EMPTY) ) { return true; } } } return false; } function merge(p) { for (let r = 0; r < p.matrix.length; r++) { for (let c = 0; c < p.matrix[r].length; c++) { if (p.matrix[r][c] !== 0) { board[p.y + r][p.x + c] = p.matrix[r][c]; } } } } function clearLines() { let cleared = 0; outer: for (let r = ROWS - 1; r >= 0; r--) { for (let c = 0; c < COLS; c++) { if (board[r][c] === 0) continue outer; } board.splice(r, 1); board.unshift(Array(COLS).fill(EMPTY)); cleared++; r++; } if (cleared > 0) { lines += cleared; score += [0, 100, 300, 500, 800][cleared] * (level + 1); level = Math.floor(lines / 10); dropInterval = Math.max(100, 1000 - level * 100); updateSidebar(); } } /* --- GAME LOOP --- */ let score = 0; let lines = 0; let level = 0; let dropCounter = 0; let dropInterval = 1000; let lastTime = 0; piece = createPiece(Math.floor(Math.random() * 7) + 1); function updateDrop(time) { const delta = time - lastTime; lastTime = time; dropCounter += delta; if (dropCounter > dropInterval) { dropPiece(); } requestAnimationFrame(updateDrop); } requestAnimationFrame(updateDrop); function dropPiece() { if (!isCollision(piece.matrix, piece.x, piece.y + 1)) { piece.y++; dropCounter = 0; draw(); } else { merge(piece); clearLines(); piece = createPiece(Math.floor(Math.random() * 7) + 1); if (isCollision(piece.matrix, piece.x, piece.y)) { alert("Game over!"); board = Array.from({ length: ROWS }, () => Array(COLS).fill(EMPTY)); score = lines = level = 0; dropInterval = 1000; updateSidebar(); } } } /* --- INPUT --- */ document.addEventListener("keydown", (e) => { switch (e.code) { case "ArrowLeft": if (!isCollision(piece.matrix, piece.x - 1, piece.y)) { piece.x--; } break; case "ArrowRight": if (!isCollision(piece.matrix, piece.x + 1, piece.y)) { piece.x++; } break; case "ArrowDown": if (!isCollision(piece.matrix, piece.x, piece.y + 1)) { piece.y++; score += 1; updateSidebar(); } break; case "ArrowUp": const rotated = rotate(piece.matrix); if (!isCollision(rotated, piece.x, piece.y)) { piece.matrix = rotated; } break; case "Space": while (!isCollision(piece.matrix, piece.x, piece.y + 1)) { piece.y++; score += 2; } dropCounter = dropInterval; break; } draw(); }); function updateSidebar() { document.getElementById("score").textContent = "Score " + score; document.getElementById("level").textContent = "Level " + level; document.getElementById("lines").textContent = "Lines " + lines; } draw(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Tetris</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #1a1a1a; font-family: Arial, sans-serif; } #game-container { display: flex; flex-direction: row; } #canvas { border: 2px solid #fff; background-color: #000; } #info { margin-left: 20px; color: #fff; } #info h2 { margin: 0; } #score, #level { font-size: 24px; } </style> </head> <body> <div id="game-container"> <canvas id="canvas" width="300" height="600"></canvas> <div id="info"> <h2>Tetris</h2> <p>Score: <span id="score">0</span></p> <p>Level: <span id="level">1</span></p> <p>Controls:</p> <ul> <li>Arrow Left/Right: Move</li> <li>Arrow Down: Soft Drop</li> <li>Arrow Up: Rotate</li> <li>Space: Hard Drop</li> </ul> </div> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const scoreElement = document.getElementById('score'); const levelElement = document.getElementById('level'); const ROW = 20; const COL = 10; const SQ = 30; const VACANT = 'black'; let board = []; for (let r = 0; r < ROW; r++) { board[r] = []; for (let c = 0; c < COL; c++) { board[r][c] = VACANT; } } function drawSquare(x, y, color) { ctx.fillStyle = color; ctx.fillRect(x * SQ, y * SQ, SQ, SQ); ctx.strokeStyle = '#333'; ctx.strokeRect(x * SQ, y * SQ, SQ, SQ); } function drawBoard() { for (let r = 0; r < ROW; r++) { for (let c = 0; c < COL; c++) { drawSquare(c, r, board[r][c]); } } } const PIECES = [ [Z, 'red'], [S, 'green'], [T, 'yellow'], [O, 'blue'], [L, 'purple'], [I, 'cyan'], [J, 'orange'] ]; function randomPiece() { let r = Math.floor(Math.random() * PIECES.length); return new Piece(PIECES[r][0], PIECES[r][1]); } class Piece { constructor(tetromino, color) { this.tetromino = tetromino; this.color = color; this.tetrominoN = 0; this.activeTetromino = this.tetromino[this.tetrominoN]; this.x = 3; this.y = -2; } fill(color) { for (let r = 0; r < this.activeTetromino.length; r++) { for (let c = 0; c < this.activeTetromino.length; c++) { if (this.activeTetromino[r][c]) { drawSquare(this.x + c, this.y + r, color); } } } } draw() { this.fill(this.color); } undraw() { this.fill(VACANT); } moveDown() { if (!this.collision(0, 1, this.activeTetromino)) { this.undraw(); this.y++; this.draw(); } else { this.lock(); p = randomPiece(); } } moveRight() { if (!this.collision(1, 0, this.activeTetromino)) { this.undraw(); this.x++; this.draw(); } } moveLeft() { if (!this.collision(-1, 0, this.activeTetromino)) { this.undraw(); this.x--; this.draw(); } } rotate() { let nextPattern = this.tetromino[(this.tetrominoN + 1) % this.tetromino.length]; let kick = 0; if (this.collision(0, 0, nextPattern)) { if (this.x > COL / 2) { kick = -1; } else { kick = 1; } } if (!this.collision(kick, 0, nextPattern)) { this.undraw(); this.x += kick; this.tetrominoN = (this.tetrominoN + 1) % this.tetromino.length; this.activeTetromino = this.tetromino[this.tetrominoN]; this.draw(); } } lock() { for (let r = 0; r < this.activeTetromino.length; r++) { for (let c = 0; c < this.activeTetromino.length; c++) { if (!this.activeTetromino[r][c]) continue; if (this.y + r < 0) { alert('Game Over'); gameOver = true; return; } board[this.y + r][this.x + c] = this.color; } } let rowCleared = 0; for (let r = 0; r < ROW; r++) { let isRowFull = true; for (let c = 0; c < COL; c++) { if (board[r][c] === VACANT) { isRowFull = false; break; } } if (isRowFull) { for (let y = r; y > 1; y--) { for (let c = 0; c < COL; c++) { board[y][c] = board[y - 1][c]; } } for (let c = 0; c < COL; c++) { board[0][c] = VACANT; } rowCleared++; } } if (rowCleared > 0) { score += rowCleared * 10 * level; scoreElement.innerHTML = score; if (score >= level * 100) { level++; levelElement.innerHTML = level; dropInterval = 1000 / level; } } drawBoard(); } collision(x, y, piece) { for (let r = 0; r < piece.length; r++) { for (let c = 0; c < piece.length; c++) { if (!piece[r][c]) continue; let newX = this.x + c + x; let newY = this.y + r + y; if (newX < 0 || newX >= COL || newY >= ROW) return true; if (newY < 0) continue; if (board[newY][newX] !== VACANT) return true; } } return false; } drop() { while (!this.collision(0, 1, this.activeTetromino)) { this.undraw(); this.y++; this.draw(); } this.lock(); p = randomPiece(); } shadowY() { let shadowY = this.y; while (!this.collision(0, shadowY - this.y + 1, this.activeTetromino)) { shadowY++; } return shadowY; } } const I = [ [[1,1,1,1]], [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], [[1,1,1,1]], [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]] ]; const J = [ [[1,0,0],[1,1,1]], [[0,1,1],[0,1,0],[0,1,0]], [[0,0,0],[1,1,1],[0,0,1]], [[0,1,0],[0,1,0],[1,1,0]] ]; const L = [ [[0,0,1],[1,1,1]], [[0,1,0],[0,1,0],[0,1,1]], [[0,0,0],[1,1,1],[1,0,0]], [[1,1,0],[0,1,0],[0,1,0]] ]; const O = [ [[1,1],[1,1]] ]; const S = [ [[0,1,1],[1,1,0]], [[0,1,0],[0,1,1],[0,0,1]], [[0,1,1],[1,1,0]], [[0,1,0],[0,1,1],[0,0,1]] ]; const T = [ [[0,1,0],[1,1,1]], [[0,1,0],[0,1,1],[0,1,0]], [[0,0,0],[1,1,1],[0,1,0]], [[0,1,0],[1,1,0],[0,1,0]] ]; const Z = [ [[1,1,0],[0,1,1]], [[0,0,1],[0,1,1],[0,1,0]], [[1,1,0],[0,1,1]], [[0,0,1],[0,1,1],[0,1,0]] ]; let p = randomPiece(); let score = 0; let level = 1; let dropInterval = 1000; let lastTime = 0; let gameOver = false; function drawShadow() { let shadowY = p.shadowY(); for (let r = 0; r < p.activeTetromino.length; r++) { for (let c = 0; c < p.activeTetromino.length; c++) { if (p.activeTetromino[r][c]) { drawSquare(p.x + c, shadowY + r, 'rgba(255,255,255,0.3)'); } } } } function gameLoop(time = 0) { if (gameOver) return; let deltaTime = time - lastTime; lastTime = time; dropCounter += deltaTime; if (dropCounter > dropInterval) { p.moveDown(); dropCounter = 0; } drawBoard(); drawShadow(); p.draw(); requestAnimationFrame(gameLoop); } let dropCounter = 0; document.addEventListener('keydown', event => { if (event.keyCode === 37) p.moveLeft(); else if (event.keyCode === 39) p.moveRight(); else if (event.keyCode === 40) p.moveDown(); else if (event.keyCode === 38) p.rotate(); else if (event.keyCode === 32) p.drop(); }); drawBoard(); gameLoop(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Modern Tetris with Ghost Piece</title> <style> body { background: #111; display: flex; align-items: flex-start; justify-content: center; height: 100vh; margin: 0; color: #fff; font-family: sans-serif; } #game { border: 4px solid #333; background: #000; } #next { border: 4px solid #333; background: #000; margin-left: 20px; } .info { margin-left: 20px; } </style> </head> <body> <canvas id="game" width="240" height="480"></canvas> <canvas id="next" width="96" height="96"></canvas> <script> const canvas = document.getElementById('game'); const ctx = canvas.getContext('2d'); const nextCanvas = document.getElementById('next'); const ctxNext = nextCanvas.getContext('2d'); const COLS = 10, ROWS = 20, BLOCK = 24; ctx.scale(BLOCK, BLOCK); ctxNext.scale(BLOCK, BLOCK); function createMatrix(w, h) { const m = []; while (h--) m.push(new Array(w).fill(0)); return m; } const SHAPES = { I: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], J: [[2,0,0],[2,2,2],[0,0,0]], L: [[0,0,3],[3,3,3],[0,0,0]], O: [[4,4],[4,4]], S: [[0,5,5],[5,5,0],[0,0,0]], T: [[0,6,0],[6,6,6],[0,0,0]], Z: [[7,7,0],[0,7,7],[0,0,0]], }; const COLORS = [null, '#00f0f0', '#0000f0', '#f0a000', '#f0f000', '#00f000', '#a000f0', '#f00000']; function rotate(matrix, dir) { for (let y = 0; y < matrix.length; ++y) for (let x = 0; x < y; ++x) [matrix[x][y], matrix[y][x]] = [matrix[y][x], matrix[x][y]]; if (dir > 0) matrix.forEach(row => row.reverse()); else matrix.reverse(); } function collide(board, piece) { const [m, o] = [piece.matrix, piece.pos]; for (let y = 0; y < m.length; ++y) for (let x = 0; x < m[y].length; ++x) if (m[y][x] && (board[y + o.y] && board[y + o.y][x + o.x]) !== 0) return true; return false; } function merge(board, piece) { piece.matrix.forEach((row, y) => row.forEach((value, x) => { if (value) board[y + piece.pos.y][x + piece.pos.x] = value; }) ); } function sweep() { let rowCount = 1; outer: for (let y = ROWS - 1; y >= 0; --y) { for (let x = 0; x < COLS; ++x) { if (board[y][x] === 0) { continue outer; } } const row = board.splice(y, 1)[0].fill(0); board.unshift(row); ++y; player.score += rowCount * 10; rowCount *= 2; } } function createPiece(type) { return { matrix: SHAPES[type].map(row => row.slice()), pos: {x: (COLS / 2 | 0) - (SHAPES[type][0].length / 2 | 0), y: 0} }; } function drawMatrix(matrix, offset, ghost=false) { matrix.forEach((row, y) => { row.forEach((value, x) => { if (value !== 0) { ctx.fillStyle = ghost ? 'rgba(255,255,255,0.3)' : COLORS[value]; ctx.fillRect(x + offset.x, y + offset.y, 1, 1); if (!ghost) { ctx.strokeStyle = '#000'; ctx.lineWidth = 0.05; ctx.strokeRect(x + offset.x, y + offset.y, 1, 1); } } }); }); } function draw() { ctx.fillStyle = '#000'; ctx.fillRect(0, 0, COLS, ROWS); drawMatrix(board, {x:0, y:0}); // draw ghost const ghostPos = {x: player.pos.x, y: player.pos.y}; while (!collide(board, {matrix: player.matrix, pos: ghostPos})) { ghostPos.y++; } ghostPos.y--; drawMatrix(player.matrix, ghostPos, true); // draw current drawMatrix(player.matrix, player.pos); drawNext(); } function drawNext() { ctxNext.fillStyle = '#000'; ctxNext.fillRect(0, 0, 4, 4); if (!next.matrix) return; next.matrix.forEach((row, y) => { row.forEach((value, x) => { if (value) { ctxNext.fillStyle = COLORS[value]; ctxNext.fillRect(x, y, 1, 1); ctxNext.strokeStyle = '#000'; ctxNext.lineWidth = 0.05; ctxNext.strokeRect(x, y, 1, 1); } }); }); } function playerReset() { const types = 'IJLOSTZ'; if (!next.matrix) { player.matrix = createPiece(types[types.length * Math.random() | 0]).matrix; } else { player.matrix = next.matrix; } next.matrix = createPiece(types[types.length * Math.random() | 0]).matrix; player.pos.y = 0; player.pos.x = (COLS / 2 | 0) - (player.matrix[0].length / 2 | 0); if (collide(board, player)) { board.forEach(row => row.fill(0)); player.score = 0; } } function playerDrop() { player.pos.y++; if (collide(board, player)) { player.pos.y--; merge(board, player); sweep(); playerReset(); } dropCounter = 0; } function playerMove(dir) { player.pos.x += dir; if (collide(board, player)) player.pos.x -= dir; } function playerRotate(dir) { const pos = player.pos.x; let offset = 1; rotate(player.matrix, dir); while (collide(board, player)) { player.pos.x += offset; offset = -(offset + (offset > 0 ? 1 : -1)); if (offset > player.matrix[0].length) { rotate(player.matrix, -dir); player.pos.x = pos; return; } } } let dropCounter = 0; let dropInterval = 1000; let lastTime = 0; function update(time = 0) { const deltaTime = time - lastTime; lastTime = time; dropCounter += deltaTime; if (dropCounter > dropInterval) playerDrop(); draw(); requestAnimationFrame(update); } document.addEventListener('keydown', event => { if (event.key === 'ArrowLeft') playerMove(-1); else if (event.key === 'ArrowRight') playerMove(1); else if (event.key === 'ArrowDown') playerDrop(); else if (event.key === 'ArrowUp') playerRotate(1); else if (event.key === ' ') { // hard drop while (!collide(board, player)) player.pos.y++; player.pos.y--; merge(board, player); sweep(); playerReset(); dropCounter = 0; } }); const board = createMatrix(COLS, ROWS); const player = {pos: {x:0,y:0}, matrix: null, score: 0}; const next = {matrix: null}; playerReset(); update(); </script> </body> </html> ```
Drag to resize
# Modern Tetris with Shadow Placement I'll create a fully functional Tetris game with the shadow placement feature that shows where the current piece will land when dropped. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Modern Tetris with Shadow Placement</title> <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap" rel="stylesheet"> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Orbitron', sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; background: linear-gradient(135deg, #1a1a2e, #16213e); color: #e2e2e2; overflow: hidden; } .game-container { display: flex; gap: 30px; padding: 30px; border-radius: 20px; background: rgba(22, 26, 41, 0.8); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); position: relative; max-width: 900px; margin: 20px; } .game-header { position: absolute; top: 20px; left: 0; right: 0; text-align: center; z-index: 10; } .title { font-size: 2.5rem; letter-spacing: 3px; color: #ffd700; text-shadow: 0 0 10px rgba(255, 215, 0, 0.8); margin-bottom: 10px; } .game-board { display: flex; flex-direction: column; align-items: center; } .board { display: grid; grid-template-columns: repeat(10, 30px); grid-template-rows: repeat(20, 30px); gap: 1px; background-color: rgba(0, 0, 0, 0.3); border: 2px solid #4a6fa5; border-radius: 4px; margin: 0 auto; position: relative; box-shadow: 0 0 20px rgba(0, 100, 200, 0.2); } .panel { display: flex; flex-direction: column; width: 180px; gap: 20px; } .info-panel { background: rgba(22, 22, 46, 0.7); padding: 20px; border-radius: 15px; border: 1px solid #4a6fa5; } .next-piece { display: grid; grid-template-columns: repeat(4, 30px); grid-template-rows: repeat(4, 30px); gap: 1px; margin-top: 10px; background-color: rgba(0, 0, 0, 0.3); border-radius: 4px; padding: 10px; justify-content: center; } .stats { text-align: center; padding: 15px 0; } .stat-value { font-size: 1.8rem; color: #00e6cc; margin: 8px 0; } .controls { margin-top: 15px; border-top: 1px solid #4a6fa5; padding-top: 15px; } .cell { width: 30px; height: 30px; border-radius: 3px; background-color: #0d1b2a; transition: background-color 0.1s ease; } .cell.filled { border: 1px solid rgba(255, 255, 255, 0.2); box-shadow: inset 3px 3px 6px rgba(0, 0, 0, 0.3), inset -3px -3px 6px rgba(255, 255, 255, 0.05); } .cell.shadow { background-color: rgba(255, 255, 255, 0.15); border: 1px dashed rgba(255, 255, 255, 0.3); } .btn { background: linear-gradient(to bottom, #4a6fa5, #2a4a7a); color: white; border: none; padding: 12px 15px; font-family: 'Orbitron', sans-serif; border-radius: 8px; cursor: pointer; outline: none; transition: all 0.3s; margin: 5px; width: 100%; letter-spacing: 1px; font-weight: bold; } .btn:hover { background: linear-gradient(to bottom, #5a7fb5, #3a5a8a); transform: translateY(-2px); } .btn:active { transform: translateY(1px); } .controls-info { margin-top: 20px; font-size: 14px; color: #a0a0c0; line-height: 1.6; } .controls-key { display: inline-block; background: rgba(0, 0, 0, 0.3); padding: 2px 8px; border-radius: 4px; border: 1px solid #4a6fa5; } h2 { color: #00e6cc; text-align: center; margin-bottom: 15px; font-size: 1.3rem; } /* Tetromino Colors */ .piece-I { background: linear-gradient(to bottom, #00f0ff, #0095b6); } .piece-O { background: linear-gradient(to bottom, #f5d142, #d4af37); } .piece-T { background: linear-gradient(to bottom, #be45d2, #8c2ba1); } .piece-S { background: linear-gradient(to bottom, #6aec67, #2a9a3c); } .piece-Z { background: linear-gradient(to bottom, #ff6b6b, #d64545); } .piece-J { background: linear-gradient(to bottom, #429ef5, #1a5ca3); } .piece-L { background: linear-gradient(to bottom, #ff9a3d, #d86a11); } @keyframes flash { 50% { opacity: 0.5; } } .flashing { animation: flash 0.2s 3; } .instructions { position: absolute; bottom: 20px; background: rgba(0, 0, 0, 0.7); padding: 15px; border-radius: 10px; text-align: center; font-size: 0.9rem; left: 50%; transform: translateX(-50%); width: 90%; max-width: 500px; } </style> </head> <body> <div class="game-header"> <h1 class="title">◢ MODERN TETRIS ◣</h1> <p>Shadow Placement Enabled</p> </div> <div class="game-container"> <div class="game-board"> <div id="board" class="board"></div> </div> <div class="panel"> <div class="info-panel"> <h2>NEXT PIECE</h2> <div id="next-piece" class="next-piece"></div> <div class="stats"> <h2>SCORE</h2> <div id="score" class="stat-value">0</div> <h2>LEVEL</h2> <div id="level" class="stat-value">1</div> <h2>LINES</h2> <div id="lines" class="stat-value">0</div> </div> </div> <button id="start-btn" class="btn">START GAME</button> <button id="pause-btn" class="btn">PAUSE</button> <div class="controls"> <h2>CONTROLS</h2> <div class="controls-info"> <p><span class="controls-key">←</span> - Move Left</p> <p><span class="controls-key">→</span> - Move Right</p> <p><span class="controls-key">↓</span> - Move Down</p> <p><span class="controls-key">↑</span> - Rotate</p> <p><span class="controls-key">SPACE</span> - Hard Drop</p> </div> </div> </div> </div> <div class="instructions"> Try to complete horizontal lines without gaps. The shadow piece shows where your tetromino will land! </div> <script> // Game constants const COLS = 10; const ROWS = 20; const BLOCK_SIZE = 30; const COLORS = ['#1a1a2e', '#16213e', '#0d1b2a']; // Game state let board = []; let currentPiece = null; let nextPiece = null; let score = 0; let level = 1; let lines = 0; let isGameOver = false; let isPaused = false; let dropInterval = null; let dropStart = Date.now(); // Tetromino shapes (tetrominos[shape][rotation][y][x]) const tetrominos = { 'I': [ [ [0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0] ], [ [0,0,1,0], [0,0,1,0], [0,0,1,0], [0,0,1,0] ], [ [0,0,0,0], [0,0,0,0], [1,1,1,1], [0,0,0,0] ], [ [0,1,0,0], [0,1,0,0], [0,1,0,0], [0,1,0,0] ] ], 'J': [ [ [1,0,0], [1,1,1], [0,0,0] ], [ [0,1,1], [0,1,0], [0,1,0] ], [ [0,0,0], [1,1,1], [0,0,1] ], [ [0,1,0], [0,1,0], [1,1,0] ] ], 'L': [ [ [0,0,1], [1,1,1], [0,0,0] ], [ [0,1,0], [0,1,0], [0,1,1] ], [ [0,0,0], [1,1,1], [1,0,0] ], [ [1,1,0], [0,1,0], [0,1,0] ] ], 'O': [ [ [1,1], [1,1] ] ], 'S': [ [ [0,1,1], [1,1,0], [0,0,0] ], [ [0,1,0], [0,1,1], [0,0,1] ], [ [0,0,0], [0,1,1], [1,1,0] ], [ [1,0,0], [1,1,0], [0,1,0] ] ], 'T': [ [ [0,1,0], [1,1,1], [0,0,0] ], [ [0,1,0], [0,1,1], [0,1,0] ], [ [0,0,0], [1,1,1], [0,1,0] ], [ [0,1,0], [1,1,0], [0,1,0] ] ], 'Z': [ [ [1,1,0], [0,1,1], [0,0,0] ], [ [0,0,1], [0,1,1], [0,1,0] ], [ [0,0,0], [1,1,0], [0,1,1] ], [ [0,1,0], [1,1,0], [1,0,0] ] ] }; // Initialize the board function initBoard() { const boardElement = document.getElementById('board'); boardElement.innerHTML = ''; for (let y = 0; y < ROWS; y++) { board[y] = []; for (let x = 0; x < COLS; x++) { const cell = document.createElement('div'); cell.classList.add('cell'); cell.dataset.row = y; cell.dataset.col = x; boardElement.appendChild(cell); board[y][x] = 0; } } } // Create a random piece function createPiece() { const types = 'IJLOSTZ'; const randomType = types[Math.floor(Math.random() * types.length)]; let rotations = tetrominos[randomType]; let rotation = Math.floor(Math.random() * rotations.length); return { type: randomType, rotation: rotation, shape: rotations[rotation], x: Math.floor(COLS / 2) - Math.floor(rotations[rotation][0].length / 2), y: 0 }; } // Draw the board with pieces and shadow function drawBoard() { // Clear the board for (let y = 0; y < ROWS; y++) { for (let x = 0; x < COLS; x++) { const cellIndex = y * COLS + x; const cell = document.getElementById('board').children[cellIndex]; // Reset classes except for the base class cell.className = 'cell'; if (board[y][x] !== 0) { cell.classList.add('filled', `piece-${board[y][x]}`); } } } // Draw the shadow if (currentPiece) { const shadowY = calculateDropPosition(); drawPiece(currentPiece.x, shadowY, currentPiece.shape, 'shadow'); } // Draw the current piece if (currentPiece) { drawPiece(currentPiece.x, currentPiece.y, currentPiece.shape, currentPiece.type); } } // Draw a piece at specified position function drawPiece(x, y, shape, type) { for (let row = 0; row < shape.length; row++) { for (let col = 0; col < shape[row].length; col++) { if (shape[row][col]) { const absX = x + col; const absY = y + row; if (absX >= 0 && absX < COLS && absY >= 0 && absY < ROWS) { const cellIndex = absY * COLS + absX; const cell = document.getElementById('board').children[cellIndex]; // Don't overwrite filled cells with shadow if (board[absY][absX] === 0) { cell.className = `cell ${type === 'shadow' ? 'shadow' : 'filled'}`; if (type !== 'shadow') { cell.classList.add(`piece-${type}`); } } } } } } } // Draw next piece preview function drawNextPiece() { const nextPieceContainer = document.getElementById('next-piece'); nextPieceContainer.innerHTML = ''; for (let y = 0; y < 4; y++) { for (let x = 0; x < 4; x++) { const cell = document.createElement('div'); cell.classList.add('cell'); if (nextPiece.shape[y]?.[x]) { cell.classList.add('filled', `piece-${nextPiece.type}`); } nextPieceContainer.appendChild(cell); } } } // Calculate where the piece will drop function calculateDropPosition() { if (!currentPiece) return currentPiece.y; let dropY = currentPiece.y; while (dropY < ROWS - currentPiece.shape.length) { if (checkCollision(currentPiece.x, dropY + 1, currentPiece.shape)) { break; } dropY++; } return dropY; } // Check for collision function checkCollision(x, y, shape) { for (let row = 0; row < shape.length; row++) { for (let col = 0; col < shape[row].length; col++) { if (shape[row][col]) { const absX = x + col; const absY = y + row; // Check if out of bounds if (absX < 0 || absX >= COLS || absY >= ROWS) { return true; } // Check if collides with filled board cell if (absY >= 0 && board[absY][absX]) { return true; } } } } return false; } // Rotate the current piece function rotatePiece() { if (!currentPiece || isGameOver || isPaused) return; const originalRotation = currentPiece.rotation; const originalShape = currentPiece.shape; // Get next rotation const rotations = tetrominos[currentPiece.type]; const nextRotation = (currentPiece.rotation + 1) % rotations.length; const nextShape = rotations[nextRotation]; currentPiece.rotation = nextRotation; currentPiece.shape = nextShape; // Wall kick - if rotation causes collision, revert if (checkCollision(currentPiece.x, currentPiece.y, nextShape)) { let kickOffset = 1; let kickSuccessful = false; // Try kicking to the left while (kickOffset <= 2) { if (!checkCollision(currentPiece.x - kickOffset, currentPiece.y, nextShape)) { currentPiece.x -= kickOffset; kickSuccessful = true; break; } // Try kicking to the right if (!checkCollision(currentPiece.x + kickOffset, currentPiece.y, nextShape)) { currentPiece.x += kickOffset; kickSuccessful = true; break; } kickOffset++; } // If still colliding, revert rotation if (!kickSuccessful) { currentPiece.rotation = originalRotation; currentPiece.shape = originalShape; } } drawBoard(); } // Move the piece horizontally function movePiece(deltaX) { if (!currentPiece || isGameOver || isPaused) return; if (!checkCollision(currentPiece.x + deltaX, currentPiece.y, currentPiece.shape)) { currentPiece.x += deltaX; drawBoard(); } } // Move the piece down function moveDown() { if (!currentPiece || isGameOver || isPaused) return; if (!checkCollision(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) { currentPiece.y++; drawBoard(); } else { lockPiece(); checkGameOver(); createNewPiece(); } dropStart = Date.now(); } // Hard drop function hardDrop() { if (!currentPiece || isGameOver || isPaused) return; const dropY = calculateDropPosition(); currentPiece.y = dropY; lockPiece(); checkGameOver(); createNewPiece(); drawBoard(); } // Lock the piece in place function lockPiece() { for (let row = 0; row < currentPiece.shape.length; row++) { for (let col = 0; col < currentPiece.shape[row].length; col++) { if (currentPiece.shape[row][col]) { const absX = currentPiece.x + col; const absY = currentPiece.y + row; if (absY >= 0) { board[absY][absX] = currentPiece.type; } } } } // Clear completed lines clearLines(); } // Clear any completed lines function clearLines() { let linesCleared = 0; for (let y = ROWS - 1; y >= 0; y--) { if (isLineComplete(y)) { // Remove the line board.splice(y, 1); // Add a new empty line at the top board.unshift(Array(COLS).fill(0)); linesCleared++; } } if (linesCleared > 0) { // Update game stats lines += linesCleared; score += [40, 100, 300, 1200][linesCleared - 1] * level; level = Math.floor(lines / 10) + 1; // Update display updateStats(); // Flash animation document.getElementById('board').classList.add('flashing'); setTimeout(() => { document.getElementById('board').classList.remove('flashing'); }, 300); } } // Check if a line is complete function isLineComplete(y) { for (let x = 0; x < COLS; x++) { if (board[y][x] === 0) { return false; } } return true; } // Create a new piece function createNewPiece() { currentPiece = nextPiece; nextPiece = createPiece(); drawNextPiece(); // Check if game over if (checkCollision(currentPiece.x, currentPiece.y, currentPiece.shape)) { endGame(); } } // Update the score display function updateStats() { document.getElementById('score').textContent = score; document.getElementById('level').textContent = level; document.getElementById('lines').textContent = lines; } // Check for game over function checkGameOver() { // Game over if the new piece doesn't fit if (currentPiece.y <= 0) { endGame(); } } // End the game function endGame() { isGameOver = true; clearInterval(dropInterval); alert(`Game Over! Your score: ${score}`); } // Handle keyboard input function handleKeyDown(e) { if (isGameOver) return; switch(e.key) { case 'ArrowLeft': movePiece(-1); break; case 'ArrowRight': movePiece(1); break; case 'ArrowDown': moveDown(); break; case 'ArrowUp': rotatePiece(); break; case ' ': hardDrop(); break; } } // Start the game function startGame() { if (dropInterval) { clearInterval(dropInterval); } initBoard(); isGameOver = false; isPaused = false; score = 0; level = 1; lines = 0; updateStats(); // Create first pieces nextPiece = createPiece(); createNewPiece(); drawBoard(); drawNextPiece(); // Set drop interval const baseDropTime = 1000; // 1 second const maxLevel = Math.max(1, Math.min(level, 20)); // Cap level at 20 const dropTime = Math.max(50, baseDropTime - (maxLevel - 1) * 50); // 50ms min dropInterval = setInterval(moveDown, dropTime); // Enable pause button document.getElementById('pause-btn').textContent = 'PAUSE'; document.getElementById('pause-btn').onclick = pauseGame; } // Pause the game function pauseGame() { isPaused = !isPaused; document.getElementById('pause-btn').textContent = isPaused ? 'RESUME' : 'PAUSE'; } // Start the game when the start button is clicked document.getElementById('start-btn').addEventListener('click', startGame); // Set up keyboard controls document.addEventListener('keydown', handleKeyDown); // Initialize the board initBoard(); // Draw initial placeholder const startMessage = document.createElement('div'); startMessage.textContent = "Click START to play!"; startMessage.style.position = "absolute"; startMessage.style.top = "50%"; startMessage.style.left = "50%"; startMessage.style.transform = "translate(-50%, -50%)"; startMessage.style.color = "#fff"; startMessage.style.fontSize = "1.5rem"; document.querySelector('.game-board').appendChild(startMessage); // Set up the next piece display for (let y = 0; y < 4; y++) { for (let x = 0; x < 4; x++) { const cell = document.createElement('div'); cell.classList.add('cell'); document.getElementById('next-piece').appendChild(cell); } } </script> </body> </html> ``` ## Features of this Modern Tetris Game: 1. **Shadow Placement**: Shows the ghost piece at the position where the current tetromino will land when dropped 2. **Complete Game Mechanics**: - All 7 tetromino shapes with rotations - Line clearing and scoring - Level progression - Game over detection 3. **Modern UI Design**: - Dark theme with dynamic gradients - Neon accents and polished controls - Responsive layout 4. **Game Controls**: - Arrow keys to move and rotate - Space bar for hard drop (instantly places the piece) - Start and pause buttons 5. **Additional Visual Elements**: - Next piece preview - Score, level, and lines indicators - Flashing animation when clearing lines The game board has a grid of 10x20 cells, with each tetromino having its own distinct color. The shadow placement helps players visualize where their piece will land, making gameplay more strategic. Just copy the entire code and run it in a browser to play!
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Modern Tetris</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Arial', sans-serif; background: linear-gradient(135deg, #1e3c72, #2a5298); display: flex; justify-content: center; align-items: center; min-height: 100vh; color: white; } .game-container { display: flex; gap: 20px; background: rgba(0, 0, 0, 0.7); padding: 20px; border-radius: 15px; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); } .game-board { border: 3px solid #00ffff; border-radius: 10px; background: rgba(0, 0, 0, 0.8); padding: 5px; } canvas { display: block; border-radius: 5px; } .side-panel { display: flex; flex-direction: column; gap: 20px; min-width: 200px; } .info-box { background: rgba(255, 255, 255, 0.1); padding: 15px; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.2); } .info-box h3 { margin-bottom: 10px; color: #00ffff; text-align: center; } .score-display { font-size: 24px; font-weight: bold; text-align: center; color: #fff; } .level-display { font-size: 18px; text-align: center; margin-top: 5px; } .lines-display { font-size: 16px; text-align: center; margin-top: 5px; } .next-piece-canvas { border: 2px solid #00ffff; border-radius: 5px; background: rgba(0, 0, 0, 0.5); } .controls { font-size: 14px; line-height: 1.5; } .controls div { margin-bottom: 8px; } .game-over { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.9); padding: 30px; border-radius: 15px; text-align: center; border: 2px solid #ff4444; display: none; } .game-over h2 { color: #ff4444; margin-bottom: 15px; } .restart-btn { background: linear-gradient(45deg, #00ffff, #0080ff); border: none; padding: 10px 20px; border-radius: 5px; color: white; font-weight: bold; cursor: pointer; margin-top: 15px; } .restart-btn:hover { background: linear-gradient(45deg, #0080ff, #00ffff); } </style> </head> <body> <div class="game-container"> <div class="game-board"> <canvas id="gameCanvas" width="300" height="600"></canvas> </div> <div class="side-panel"> <div class="info-box"> <h3>Score</h3> <div class="score-display" id="score">0</div> <div class="level-display">Level: <span id="level">1</span></div> <div class="lines-display">Lines: <span id="lines">0</span></div> </div> <div class="info-box"> <h3>Next Piece</h3> <canvas id="nextCanvas" width="120" height="120" class="next-piece-canvas"></canvas> </div> <div class="info-box"> <h3>Controls</h3> <div class="controls"> <div>← → Move</div> <div>↓ Soft Drop</div> <div>↑ Rotate</div> <div>Space Hard Drop</div> <div>C Hold Piece</div> <div>P Pause</div> </div> </div> </div> </div> <div class="game-over" id="gameOver"> <h2>Game Over</h2> <div>Final Score: <span id="finalScore">0</span></div> <button class="restart-btn" onclick="restartGame()">Play Again</button> </div> <script> const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); const nextCanvas = document.getElementById('nextCanvas'); const nextCtx = nextCanvas.getContext('2d'); const ROWS = 20; const COLS = 10; const BLOCK_SIZE = 30; // Tetris pieces const PIECES = { I: { shape: [[1,1,1,1]], color: '#00ffff' }, O: { shape: [ [1,1], [1,1] ], color: '#ffff00' }, T: { shape: [ [0,1,0], [1,1,1] ], color: '#800080' }, S: { shape: [ [0,1,1], [1,1,0] ], color: '#00ff00' }, Z: { shape: [ [1,1,0], [0,1,1] ], color: '#ff0000' }, J: { shape: [ [1,0,0], [1,1,1] ], color: '#0000ff' }, L: { shape: [ [0,0,1], [1,1,1] ], color: '#ffa500' } }; const PIECE_NAMES = Object.keys(PIECES); class Tetris { constructor() { this.board = Array(ROWS).fill().map(() => Array(COLS).fill(0)); this.score = 0; this.lines = 0; this.level = 1; this.dropTime = 0; this.dropInterval = 1000; this.currentPiece = null; this.nextPiece = null; this.heldPiece = null; this.canHold = true; this.gameRunning = true; this.paused = false; this.spawnPiece(); this.generateNextPiece(); } spawnPiece() { if (!this.nextPiece) { this.generateNextPiece(); } this.currentPiece = this.nextPiece; this.generateNextPiece(); this.canHold = true; // Center the piece horizontally this.currentPiece.x = Math.floor((COLS - this.currentPiece.shape[0].length) / 2); this.currentPiece.y = 0; if (this.collision()) { this.gameOver(); } } generateNextPiece() { const pieceType = PIECE_NAMES[Math.floor(Math.random() * PIECE_NAMES.length)]; const piece = PIECES[pieceType]; this.nextPiece = { shape: piece.shape.map(row => [...row]), color: piece.color, x: 0, y: 0 }; } collision(piece = this.currentPiece, dx = 0, dy = 0) { for (let y = 0; y < piece.shape.length; y++) { for (let x = 0; x < piece.shape[y].length; x++) { if (piece.shape[y][x]) { const newX = piece.x + x + dx; const newY = piece.y + y + dy; if (newX < 0 || newX >= COLS || newY >= ROWS) { return true; } if (newY >= 0 && this.board[newY][newX]) { return true; } } } } return false; } rotate() { const rotated = this.currentPiece.shape[0].map((_, index) => this.currentPiece.shape.map(row => row[index]).reverse() ); const backup = this.currentPiece.shape; this.currentPiece.shape = rotated; if (this.collision()) { this.currentPiece.shape = backup; } else { // Play rotation sound effect could go here } } move(dx) { if (!this.collision(this.currentPiece, dx, 0)) { this.currentPiece.x += dx; } } drop() { if (!this.collision(this.currentPiece, 0, 1)) { this.currentPiece.y += 1; } else { this.placePiece(); this.clearLines(); this.spawnPiece(); } } hardDrop() { while (!this.collision(this.currentPiece, 0, 1)) { this.currentPiece.y += 1; this.score += 2; // Points for hard drop } this.drop(); } placePiece() { for (let y = 0; y < this.currentPiece.shape.length; y++) { for (let x = 0; x < this.currentPiece.shape[y].length; x++) { if (this.currentPiece.shape[y][x]) { const boardY = this.currentPiece.y + y; const boardX = this.currentPiece.x + x; if (boardY >= 0) { this.board[boardY][boardX] = this.currentPiece.color; } } } } } clearLines() { let linesCleared = 0; for (let y = ROWS - 1; y >= 0; y--) { if (this.board[y].every(cell => cell !== 0)) { this.board.splice(y, 1); this.board.unshift(Array(COLS).fill(0)); linesCleared++; y++; // Check the same line again } } if (linesCleared > 0) { this.lines += linesCleared; this.score += [0, 100, 300, 500, 800][linesCleared] * this.level; this.level = Math.floor(this.lines / 10) + 1; this.dropInterval = Math.max(50, 1000 - (this.level - 1) * 50); this.updateScore(); } } holdPiece() { if (!this.canHold) return; if (!this.heldPiece) { this.heldPiece = { shape: this.currentPiece.shape.map(row => [...row]), color: this.currentPiece.color }; this.spawnPiece(); } else { const temp = { shape: this.currentPiece.shape.map(row => [...row]), color: this.currentPiece.color }; this.currentPiece = { shape: this.heldPiece.shape.map(row => [...row]), color: this.heldPiece.color, x: Math.floor((COLS - this.heldPiece.shape[0].length) / 2), y: 0 }; this.heldPiece = temp; } this.canHold = false; } getShadowPosition() { const shadow = { ...this.currentPiece, shape: this.currentPiece.shape.map(row => [...row]) }; while (!this.collision(shadow, 0, 1)) { shadow.y += 1; } return shadow; } draw() { // Clear canvas ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height); // Draw board for (let y = 0; y < ROWS; y++) { for (let x = 0; x < COLS; x++) { if (this.board[y][x]) { ctx.fillStyle = this.board[y][x]; ctx.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1); } } } // Draw shadow if (this.currentPiece) { const shadow = this.getShadowPosition(); ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; for (let y = 0; y < shadow.shape.length; y++) { for (let x = 0; x < shadow.shape[y].length; x++) { if (shadow.shape[y][x]) { ctx.fillRect( (shadow.x + x) * BLOCK_SIZE, (shadow.y + y) * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1 ); } } } } // Draw current piece if (this.currentPiece) { ctx.fillStyle = this.currentPiece.color; for (let y = 0; y < this.currentPiece.shape.length; y++) { for (let x = 0; x < this.currentPiece.shape[y].length; x++) { if (this.currentPiece.shape[y][x]) { ctx.fillRect( (this.currentPiece.x + x) * BLOCK_SIZE, (this.currentPiece.y + y) * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1 ); } } } } // Draw grid ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; ctx.lineWidth = 1; for (let x = 0; x <= COLS; x++) { ctx.beginPath(); ctx.moveTo(x * BLOCK_SIZE, 0); ctx.lineTo(x * BLOCK_SIZE, canvas.height); ctx.stroke(); } for (let y = 0; y <= ROWS; y++) { ctx.beginPath(); ctx.moveTo(0, y * BLOCK_SIZE); ctx.lineTo(canvas.width, y * BLOCK_SIZE); ctx.stroke(); } } drawNextPiece() { nextCtx.fillStyle = '#000'; nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height); if (this.nextPiece) { const blockSize = 25; const offsetX = (nextCanvas.width - this.nextPiece.shape[0].length * blockSize) / 2; const offsetY = (nextCanvas.height - this.nextPiece.shape.length * blockSize) / 2; nextCtx.fillStyle = this.nextPiece.color; for (let y = 0; y < this.nextPiece.shape.length; y++) { for (let x = 0; x < this.nextPiece.shape[y].length; x++) { if (this.nextPiece.shape[y][x]) { nextCtx.fillRect( offsetX + x * blockSize, offsetY + y * blockSize, blockSize - 1, blockSize - 1 ); } } } } } updateScore() { document.getElementById('score').textContent = this.score; document.getElementById('level').textContent = this.level; document.getElementById('lines').textContent = this.lines; } gameOver() { this.gameRunning = false; document.getElementById('finalScore').textContent = this.score; document.getElementById('gameOver').style.display = 'block'; } update(deltaTime) { if (!this.gameRunning || this.paused) return; this.dropTime += deltaTime; if (this.dropTime > this.dropInterval) { this.drop(); this.dropTime = 0; } } } let game = new Tetris(); let lastTime = 0; function gameLoop(currentTime) { const deltaTime = currentTime - lastTime; lastTime = currentTime; game.update(deltaTime); game.draw(); game.drawNextPiece(); requestAnimationFrame(gameLoop); } // Controls document.addEventListener('keydown', (e) => { if (!game.gameRunning) return; switch(e.key) { case 'ArrowLeft': e.preventDefault(); game.move(-1); break; case 'ArrowRight': e.preventDefault(); game.move(1); break; case 'ArrowDown': e.preventDefault(); game.drop(); game.score += 1; game.updateScore(); break; case 'ArrowUp': e.preventDefault(); game.rotate(); break; case ' ': e.preventDefault(); game.hardDrop(); break; case 'c': case 'C': e.preventDefault(); game.holdPiece(); break; case 'p': case 'P': e.preventDefault(); game.paused = !game.paused; break; } }); function restartGame() { document.getElementById('gameOver').style.display = 'none'; game = new Tetris(); game.updateScore(); } // Start the game game.updateScore(); requestAnimationFrame(gameLoop); </script> </body> </html> ``` This is a fully functional modern Tetris game with all the classic features: **Features:** - **Shadow piece** - Shows where the current piece will land - **Next piece preview** - Shows the upcoming piece - **Hold function** - Press 'C' to hold/swap pieces - **Score system** - Points for lines cleared and drops - **Level progression** - Speed increases every 10 lines - **Modern UI** - Glassmorphism design with gradients - **Responsive controls** - All standard Tetris controls **Controls:** - ← → Arrow keys: Move left/right - ↓ Arrow key: Soft drop - ↑ Arrow key: Rotate piece - Space: Hard drop - C: Hold/swap piece - P: Pause game **Game mechanics:** - Standard Tetris piece rotation and movement - Line clearing with proper scoring (100/300/500/800 points) - Increasing speed with level progression - Game over detection - Visual shadow showing drop position The game features a modern glass-effect UI with neon accents, smooth animations, and all the quality-of-life features you'd expect from a modern Tetris implementation.
Drag to resize