From 867611ab45df85e1fb90de574c1644a7f40b2a8e Mon Sep 17 00:00:00 2001 From: OpenClaw Xiami Date: Sat, 28 Feb 2026 15:56:49 +0000 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4=EF=BC=9A?= =?UTF-8?q?=E7=BD=91=E9=A1=B5=E7=89=88=E4=BF=84=E7=BD=97=E6=96=AF=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E6=B8=B8=E6=88=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 HTML + CSS + JavaScript 实现 - 实现完整的游戏逻辑:方块移动、旋转、消行、计分 - 支持等级系统和加速机制 - 响应式设计,支持移动端 - 下一个方块预览功能 - 游戏暂停/继续功能 --- index.html | 60 +++++++++ script.js | 386 +++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 180 +++++++++++++++++++++++++ 3 files changed, 626 insertions(+) create mode 100644 index.html create mode 100644 script.js create mode 100644 style.css diff --git a/index.html b/index.html new file mode 100644 index 0000000..a7c0b8f --- /dev/null +++ b/index.html @@ -0,0 +1,60 @@ + + + + + + 俄罗斯方块 - Tetris + + + +
+
+

🎮 俄罗斯方块

+
+ +
+
+
+

分数

+
0
+
+
+

等级

+
1
+
+
+

消除行数

+
0
+
+
+

下一个

+ +
+
+ +
+ + +
+ +
+

操作说明

+
    +
  • ← → 左右移动
  • +
  • ↑ 旋转
  • +
  • ↓ 加速下落
  • +
  • 空格 直接落下
  • +
  • P 暂停/继续
  • +
+ +
+
+
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..046d0d9 --- /dev/null +++ b/script.js @@ -0,0 +1,386 @@ +// 游戏常量 +const COLS = 10; +const ROWS = 20; +const BLOCK_SIZE = 30; + +// 方块形状定义 +const SHAPES = [ + [[1, 1, 1, 1]], // I + [[1, 1], [1, 1]], // O + [[0, 1, 0], [1, 1, 1]], // T + [[1, 0, 0], [1, 1, 1]], // L + [[0, 0, 1], [1, 1, 1]], // J + [[0, 1, 1], [1, 1, 0]], // S + [[1, 1, 0], [0, 1, 1]] // Z +]; + +// 方块颜色 +const COLORS = [ + '#00f0f0', // I - 青色 + '#f0f000', // O - 黄色 + '#a000f0', // T - 紫色 + '#f0a000', // L - 橙色 + '#0000f0', // J - 蓝色 + '#00f000', // S - 绿色 + '#f00000' // Z - 红色 +]; + +// 游戏状态 +let canvas, ctx, nextCanvas, nextCtx; +let board = []; +let currentPiece = null; +let nextPiece = null; +let score = 0; +let level = 1; +let lines = 0; +let gameLoop = null; +let isPaused = false; +let isGameOver = false; +let dropInterval = 1000; + +// 初始化 +function init() { + canvas = document.getElementById('game-canvas'); + ctx = canvas.getContext('2d'); + nextCanvas = document.getElementById('next-canvas'); + nextCtx = nextCanvas.getContext('2d'); + + // 初始化游戏板 + for (let r = 0; r < ROWS; r++) { + board[r] = []; + for (let c = 0; c < COLS; c++) { + board[r][c] = 0; + } + } + + // 绑定事件 + document.addEventListener('keydown', handleKeyPress); + document.getElementById('start-btn').addEventListener('click', startGame); + document.getElementById('restart-btn').addEventListener('click', startGame); + + // 绘制初始游戏板 + drawBoard(); +} + +// 创建新方块 +function createPiece() { + const shapeIndex = Math.floor(Math.random() * SHAPES.length); + return { + shape: SHAPES[shapeIndex], + color: COLORS[shapeIndex], + x: Math.floor(COLS / 2) - Math.floor(SHAPES[shapeIndex][0].length / 2), + y: 0 + }; +} + +// 绘制方块 +function drawBlock(ctx, x, y, color, size = BLOCK_SIZE) { + ctx.fillStyle = color; + ctx.fillRect(x * size, y * size, size, size); + + // 添加3D效果 + ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; + ctx.fillRect(x * size, y * size, size, 2); + ctx.fillRect(x * size, y * size, 2, size); + + ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; + ctx.fillRect(x * size + size - 2, y * size, 2, size); + ctx.fillRect(x * size, y * size + size - 2, size, 2); +} + +// 绘制游戏板 +function drawBoard() { + // 清空画布 + ctx.fillStyle = '#1a1a2e'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // 绘制网格 + ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + ctx.strokeRect(c * BLOCK_SIZE, r * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE); + } + } + + // 绘制已放置的方块 + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + if (board[r][c]) { + drawBlock(ctx, c, r, board[r][c]); + } + } + } +} + +// 绘制当前方块 +function drawPiece() { + if (!currentPiece) return; + + for (let r = 0; r < currentPiece.shape.length; r++) { + for (let c = 0; c < currentPiece.shape[r].length; c++) { + if (currentPiece.shape[r][c]) { + drawBlock(ctx, currentPiece.x + c, currentPiece.y + r, currentPiece.color); + } + } + } +} + +// 绘制下一个方块 +function drawNextPiece() { + if (!nextPiece) return; + + nextCtx.fillStyle = '#fff'; + nextCtx.fillRect(0, 0, nextCanvas.width, nextCanvas.height); + + const size = 20; + const offsetX = (nextCanvas.width - nextPiece.shape[0].length * size) / 2; + const offsetY = (nextCanvas.height - nextPiece.shape.length * size) / 2; + + for (let r = 0; r < nextPiece.shape.length; r++) { + for (let c = 0; c < nextPiece.shape[r].length; c++) { + if (nextPiece.shape[r][c]) { + nextCtx.fillStyle = nextPiece.color; + nextCtx.fillRect(offsetX + c * size, offsetY + r * size, size, size); + nextCtx.strokeStyle = 'rgba(0, 0, 0, 0.3)'; + nextCtx.strokeRect(offsetX + c * size, offsetY + r * size, size, size); + } + } + } +} + +// 检测碰撞 +function collide(piece, offsetX = 0, offsetY = 0) { + for (let r = 0; r < piece.shape.length; r++) { + for (let c = 0; c < piece.shape[r].length; c++) { + if (piece.shape[r][c]) { + const newX = piece.x + c + offsetX; + const newY = piece.y + r + offsetY; + + if (newX < 0 || newX >= COLS || newY >= ROWS) { + return true; + } + + if (newY >= 0 && board[newY][newX]) { + return true; + } + } + } + } + return false; +} + +// 锁定方块 +function lockPiece() { + for (let r = 0; r < currentPiece.shape.length; r++) { + for (let c = 0; c < currentPiece.shape[r].length; c++) { + if (currentPiece.shape[r][c]) { + if (currentPiece.y + r < 0) { + gameOver(); + return; + } + board[currentPiece.y + r][currentPiece.x + c] = currentPiece.color; + } + } + } + + clearLines(); + currentPiece = nextPiece; + nextPiece = createPiece(); + drawNextPiece(); + + if (collide(currentPiece)) { + gameOver(); + } +} + +// 消除行 +function clearLines() { + let linesCleared = 0; + + for (let r = ROWS - 1; r >= 0; r--) { + if (board[r].every(cell => cell !== 0)) { + board.splice(r, 1); + board.unshift(new Array(COLS).fill(0)); + linesCleared++; + r++; + } + } + + if (linesCleared > 0) { + updateScore(linesCleared); + } +} + +// 更新分数 +function updateScore(linesCleared) { + const points = [0, 100, 300, 500, 800]; + score += points[linesCleared] * level; + lines += linesCleared; + + // 升级 + if (lines >= level * 10) { + level++; + dropInterval = Math.max(100, 1000 - (level - 1) * 100); + resetGameLoop(); + } + + updateDisplay(); +} + +// 更新显示 +function updateDisplay() { + document.getElementById('score').textContent = score; + document.getElementById('level').textContent = level; + document.getElementById('lines').textContent = lines; +} + +// 移动方块 +function movePiece(offsetX, offsetY) { + if (!currentPiece || isPaused || isGameOver) return; + + if (!collide(currentPiece, offsetX, offsetY)) { + currentPiece.x += offsetX; + currentPiece.y += offsetY; + draw(); + } +} + +// 旋转方块 +function rotatePiece() { + if (!currentPiece || isPaused || isGameOver) return; + + const rotated = currentPiece.shape[0].map((_, index) => + currentPiece.shape.map(row => row[index]).reverse() + ); + + const originalShape = currentPiece.shape; + currentPiece.shape = rotated; + + // 墙踢 - 尝试调整位置 + const kicks = [0, -1, 1, -2, 2]; + for (let kick of kicks) { + if (!collide(currentPiece, kick, 0)) { + currentPiece.x += kick; + draw(); + return; + } + } + + // 无法旋转,恢复原状 + currentPiece.shape = originalShape; +} + +// 硬降落 +function hardDrop() { + if (!currentPiece || isPaused || isGameOver) return; + + while (!collide(currentPiece, 0, 1)) { + currentPiece.y++; + score += 2; + } + lockPiece(); + updateDisplay(); + draw(); +} + +// 绘制 +function draw() { + drawBoard(); + drawPiece(); +} + +// 游戏循环 +function gameStep() { + if (!isPaused && !isGameOver) { + if (!collide(currentPiece, 0, 1)) { + currentPiece.y++; + draw(); + } else { + lockPiece(); + } + } +} + +// 开始游戏循环 +function startGameLoop() { + gameLoop = setInterval(gameStep, dropInterval); +} + +// 重置游戏循环 +function resetGameLoop() { + clearInterval(gameLoop); + startGameLoop(); +} + +// 开始游戏 +function startGame() { + // 重置状态 + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + board[r][c] = 0; + } + } + score = 0; + level = 1; + lines = 0; + dropInterval = 1000; + isPaused = false; + isGameOver = false; + + // 隐藏游戏结束界面 + document.getElementById('game-over').classList.add('hidden'); + + // 创建方块 + currentPiece = createPiece(); + nextPiece = createPiece(); + + updateDisplay(); + drawNextPiece(); + draw(); + startGameLoop(); +} + +// 游戏结束 +function gameOver() { + isGameOver = true; + clearInterval(gameLoop); + document.getElementById('final-score').textContent = score; + document.getElementById('game-over').classList.remove('hidden'); +} + +// 键盘事件处理 +function handleKeyPress(e) { + if (isGameOver) return; + + switch (e.key) { + case 'ArrowLeft': + e.preventDefault(); + movePiece(-1, 0); + break; + case 'ArrowRight': + e.preventDefault(); + movePiece(1, 0); + break; + case 'ArrowDown': + e.preventDefault(); + movePiece(0, 1); + score += 1; + updateDisplay(); + break; + case 'ArrowUp': + e.preventDefault(); + rotatePiece(); + break; + case ' ': + e.preventDefault(); + hardDrop(); + break; + case 'p': + case 'P': + isPaused = !isPaused; + break; + } +} + +// 启动 +window.onload = init; diff --git a/style.css b/style.css new file mode 100644 index 0000000..db28c27 --- /dev/null +++ b/style.css @@ -0,0 +1,180 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.game-container { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 30px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + max-width: 900px; +} + +.header { + text-align: center; + margin-bottom: 30px; +} + +.header h1 { + font-size: 2.5rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.game-area { + display: flex; + gap: 30px; + justify-content: center; + flex-wrap: wrap; +} + +.info-panel { + display: flex; + flex-direction: column; + gap: 15px; +} + +.info-box { + background: #f8f9fa; + padding: 15px; + border-radius: 10px; + text-align: center; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.info-box h3 { + font-size: 0.9rem; + color: #666; + margin-bottom: 5px; +} + +.info-value { + font-size: 1.5rem; + font-weight: bold; + color: #667eea; +} + +#next-canvas { + background: #fff; + border: 2px solid #667eea; + border-radius: 8px; +} + +.canvas-container { + position: relative; +} + +#game-canvas { + background: #1a1a2e; + border: 3px solid #667eea; + border-radius: 10px; + display: block; +} + +.game-over { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(255, 255, 255, 0.98); + padding: 40px; + border-radius: 15px; + text-align: center; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + z-index: 10; +} + +.game-over.hidden { + display: none; +} + +.game-over h2 { + font-size: 2rem; + color: #e74c3c; + margin-bottom: 15px; +} + +.game-over p { + font-size: 1.2rem; + margin-bottom: 20px; + color: #333; +} + +.controls-panel { + background: #f8f9fa; + padding: 20px; + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + min-width: 200px; +} + +.controls-panel h3 { + color: #667eea; + margin-bottom: 15px; + text-align: center; +} + +.controls-panel ul { + list-style: none; +} + +.controls-panel li { + padding: 8px 0; + color: #555; + font-size: 0.9rem; +} + +.btn { + width: 100%; + padding: 12px 24px; + margin-top: 20px; + font-size: 1rem; + font-weight: bold; + color: white; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + border-radius: 8px; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; +} + +.btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4); +} + +.btn:active { + transform: translateY(0); +} + +@media (max-width: 900px) { + .game-area { + flex-direction: column; + align-items: center; + } + + .info-panel { + flex-direction: row; + flex-wrap: wrap; + justify-content: center; + } + + .controls-panel { + min-width: 100%; + text-align: center; + } +}