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>
```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>
``````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>
``````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>
``````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>
```# 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!```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.