A comprehensive guide to the Tetris game implementation with modern React and smooth animations
This Tetris implementation is built with React and features modern animations, smooth gameplay, and a responsive design. The game follows classic Tetris rules with all seven standard tetrominoes and includes features like ghost pieces, line clearing, and progressive difficulty.
The game includes all seven standard Tetris pieces, each with distinct shapes and rotation patterns:
1const SHAPES = [
2 [[1, 1, 1, 1]], // I-piece (Cyan)
3 [[1, 1], [1, 1]], // O-piece (Yellow)
4 [[1, 1, 1], [0, 1, 0]], // T-piece (Purple)
5 [[1, 1, 1], [1, 0, 0]], // L-piece (Orange)
6 [[1, 1, 1], [0, 0, 1]], // J-piece (Blue)
7 [[1, 1, 0], [0, 1, 1]], // S-piece (Green)
8 [[0, 1, 1], [1, 1, 0]] // Z-piece (Red)
9];
The collision system checks for boundary violations and overlaps with placed pieces:
1const checkCollision = (piece, position, boardToCheck = board) => {
2 if (!piece) return true;
3
4 for (let y = 0; y < piece.shape.length; y++) {
5 for (let x = 0; x < piece.shape[y].length; x++) {
6 if (piece.shape[y][x]) {
7 const newX = position.x + x;
8 const newY = position.y + y;
9
10 // Check boundaries
11 if (newX < 0 || newX >= COLS || newY >= ROWS) {
12 return true;
13 }
14
15 // Check overlap with existing pieces
16 if (newY >= 0 && boardToCheck[newY][newX].value) {
17 return true;
18 }
19 }
20 }
21 }
22 return false;
23};
When lines are completed, they are removed and new empty lines are added at the top:
1// Check for completed lines
2let completedLines = [];
3newBoard.forEach((row, y) => {
4 if (row.every(cell => cell.value)) {
5 completedLines.push(y);
6 }
7});
8
9// Remove completed lines
10if (completedLines.length > 0) {
11 completedLines.forEach(lineY => {
12 newBoard.splice(lineY, 1);
13 newBoard.unshift(Array(COLS).fill({ value: 0, color: null }));
14 });
15
16 // Calculate score
17 const points = [0, 40, 100, 300, 1200][completedLines.length] * level;
18 setScore(prev => prev + points);
19}
The game uses setInterval to create a continuous game loop that drops pieces automatically:
1useEffect(() => {
2 if (gameState !== 'playing') return;
3
4 const gameLoop = setInterval(() => {
5 if (!movePiece(0, 1)) {
6 lockPiece();
7 }
8 }, dropIntervalRef.current);
9
10 gameLoopRef.current = gameLoop;
11 return () => clearInterval(gameLoop);
12}, [gameState, movePiece, lockPiece, level]);
All piece movements go through collision detection before being applied:
1const movePiece = useCallback((dx, dy) => {
2 if (!currentPiece || gameState !== 'playing') return false;
3
4 const newPosition = { x: currentPosition.x + dx, y: currentPosition.y + dy };
5
6 if (!checkCollision(currentPiece, newPosition)) {
7 setCurrentPosition(newPosition);
8 return true;
9 }
10 return false;
11}, [currentPiece, currentPosition, checkCollision, gameState]);
The game manages multiple pieces of state using React hooks:
1// Board and piece state
2const [board, setBoard] = useState(initialBoard);
3const [currentPiece, setCurrentPiece] = useState(null);
4const [currentPosition, setCurrentPosition] = useState({ x: 0, y: 0 });
5const [nextPiece, setNextPiece] = useState(null);
6
7// Game statistics
8const [score, setScore] = useState(0);
9const [level, setLevel] = useState(1);
10const [lines, setLines] = useState(0);
11
12// Game flow control
13const [gameState, setGameState] = useState('ready');
14const [highScore, setHighScore] = useState(0);
High scores are saved to localStorage for persistence across sessions:
1const [highScore, setHighScore] = useState(() => {
2 if (typeof window !== 'undefined') {
3 return parseInt(localStorage.getItem('tetrisHighScore') || '0');
4 }
5 return 0;
6});
7
8// Save new high score
9if (score > highScore) {
10 setHighScore(score);
11 localStorage.setItem('tetrisHighScore', score.toString());
12}
Each block is rendered with absolute positioning and gradient colors:
1<motion.div
2 key={`${x}-${y}`}
3 initial={{ scale: 0 }}
4 animate={{ scale: 1 }}
5 className={cn(
6 "absolute rounded-sm bg-gradient-to-br shadow-lg",
7 "border border-white/20",
8 cell.color
9 )}
10 style={{
11 left: `${x * BLOCK_SIZE}px`,
12 top: `${y * BLOCK_SIZE}px`,
13 width: `${BLOCK_SIZE - 2}px`,
14 height: `${BLOCK_SIZE - 2}px`,
15 }}
16/>
The ghost piece shows where the current piece will land with reduced opacity:
1{currentPiece && ghostPosition && gameState === 'playing' && (
2 currentPiece.shape.map((row, y) =>
3 row.map((value, x) => value ? (
4 <div
5 key={`ghost-${x}-${y}`}
6 className="absolute rounded-sm border-2 border-white/20 opacity-20"
7 style={{
8 left: `${(ghostPosition.x + x) * BLOCK_SIZE}px`,
9 top: `${(ghostPosition.y + y) * BLOCK_SIZE}px`,
10 width: `${BLOCK_SIZE - 4}px`,
11 height: `${BLOCK_SIZE - 4}px`,
12 }}
13 />
14 ) : null)
15 )
16)}
The game listens for keyboard events and prevents default browser behavior:
1const handleKeyPress = (e) => {
2 if (gameState !== 'playing') return;
3
4 switch (e.key) {
5 case 'ArrowLeft':
6 e.preventDefault();
7 movePiece(-1, 0);
8 break;
9 case 'ArrowRight':
10 e.preventDefault();
11 movePiece(1, 0);
12 break;
13 case 'ArrowDown':
14 e.preventDefault();
15 if (movePiece(0, 1)) {
16 setScore(prev => prev + 1);
17 }
18 break;
19 case 'ArrowUp':
20 e.preventDefault();
21 rotatePiece();
22 break;
23 case ' ':
24 e.preventDefault();
25 hardDrop();
26 break;
27 }
28};
This documentation covers the complete implementation of the Tetris game. The source code demonstrates modern React patterns, state management, and game development concepts.