Spaces:
Running
Running
Download index.html from alex080907/test: direct link, hf CLI and curl.
- Browser
- Download file 27.2 kB
-
https://huggingface.co/spaces/alex080907/test/resolve/main/index.html
- Command line
-
hf download hf://spaces/alex080907/test/index.html
-
curl -L -o index.html https://huggingface.co/spaces/alex080907/test/resolve/main/index.html
27.2 kB
| <html lang="zh"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>精致俄罗斯方块</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #f43f5e; | |
| --dark: #1e293b; | |
| --light: #f8fafc; | |
| --gray: #94a3b8; | |
| --success: #10b981; | |
| } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #f1f5f9; | |
| color: var(--dark); | |
| touch-action: manipulation; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| .game-container { | |
| perspective: 1000px; | |
| } | |
| .tetris-board { | |
| background: linear-gradient(145deg, #ffffff, #e2e8f0); | |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| border-radius: 12px; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .cell { | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| transition: all 0.2s ease; | |
| } | |
| .cell.filled { | |
| box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2); | |
| } | |
| .piece-i { background-color: #06b6d4; } | |
| .piece-j { background-color: #3b82f6; } | |
| .piece-l { background-color: #f59e0b; } | |
| .piece-o { background-color: #10b981; } | |
| .piece-s { background-color: #ef4444; } | |
| .piece-t { background-color: #8b5cf6; } | |
| .piece-z { background-color: #ec4899; } | |
| .next-piece-container { | |
| background: rgba(255, 255, 255, 0.8); | |
| backdrop-filter: blur(10px); | |
| border-radius: 8px; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| } | |
| .control-btn { | |
| background: rgba(255, 255, 255, 0.9); | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| border-radius: 50%; | |
| width: 60px; | |
| height: 60px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: all 0.2s ease; | |
| } | |
| .control-btn:active { | |
| transform: scale(0.95); | |
| box-shadow: 0 2px 3px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| .game-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(30, 41, 59, 0.8); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 12px; | |
| z-index: 10; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.3s ease; | |
| } | |
| .game-overlay.active { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .pause-btn { | |
| position: absolute; | |
| top: 16px; | |
| right: 16px; | |
| background: rgba(255, 255, 255, 0.9); | |
| width: 48px; | |
| height: 48px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| cursor: pointer; | |
| z-index: 5; | |
| } | |
| .score-display { | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(10px); | |
| border-radius: 8px; | |
| padding: 12px 16px; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| } | |
| @media (max-width: 640px) { | |
| .control-btn { | |
| width: 50px; | |
| height: 50px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="w-full max-w-md mx-auto"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h1 class="text-2xl font-bold text-gray-800">精致俄罗斯方块</h1> | |
| <div class="score-display flex items-center space-x-4"> | |
| <div class="text-center"> | |
| <p class="text-xs text-gray-500">分数</p> | |
| <p class="font-bold text-gray-800 text-lg" id="score">0</p> | |
| </div> | |
| <div class="text-center"> | |
| <p class="text-xs text-gray-500">等级</p> | |
| <p class="font-bold text-gray-800 text-lg" id="level">1</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="game-container relative"> | |
| <div class="tetris-board w-full aspect-[10/20] relative" id="board"> | |
| <!-- 游戏板将通过JS生成 --> | |
| </div> | |
| <div class="pause-btn" id="pauseBtn"> | |
| <i class="fas fa-pause text-gray-700"></i> | |
| </div> | |
| <div class="game-overlay" id="gameOverlay"> | |
| <div class="bg-white rounded-xl p-6 w-full max-w-xs text-center"> | |
| <h2 class="text-2xl font-bold mb-2" id="overlayTitle">游戏暂停</h2> | |
| <p class="text-gray-600 mb-6" id="overlayText">点击继续按钮返回游戏</p> | |
| <button class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-3 px-4 rounded-lg font-medium transition" id="overlayBtn"> | |
| 继续游戏 | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-between items-center mt-4"> | |
| <div class="next-piece-container p-3"> | |
| <p class="text-xs text-gray-500 mb-1">下一个</p> | |
| <div class="w-16 h-16" id="nextPiece"> | |
| <!-- 下一个方块将通过JS生成 --> | |
| </div> | |
| </div> | |
| <div class="flex space-x-4"> | |
| <button class="control-btn" id="rotateBtn"> | |
| <i class="fas fa-rotate text-gray-700 text-xl"></i> | |
| </button> | |
| <button class="control-btn bg-indigo-600" id="dropBtn"> | |
| <i class="fas fa-arrow-down text-white text-xl"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-3 gap-4 mt-6"> | |
| <button class="control-btn mx-auto" id="leftBtn"> | |
| <i class="fas fa-arrow-left text-gray-700 text-xl"></i> | |
| </button> | |
| <button class="control-btn mx-auto" id="downBtn"> | |
| <i class="fas fa-arrow-down text-gray-700 text-xl"></i> | |
| </button> | |
| <button class="control-btn mx-auto" id="rightBtn"> | |
| <i class="fas fa-arrow-right text-gray-700 text-xl"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // 游戏常量 | |
| const ROWS = 20; | |
| const COLS = 10; | |
| const BLOCK_SIZE = 30; | |
| const COLORS = [ | |
| null, | |
| '#06b6d4', // I - 青色 | |
| '#3b82f6', // J - 蓝色 | |
| '#f59e0b', // L - 橙色 | |
| '#10b981', // O - 绿色 | |
| '#ef4444', // S - 红色 | |
| '#8b5cf6', // T - 紫色 | |
| '#ec4899' // Z - 粉色 | |
| ]; | |
| // 方块形状定义 | |
| const SHAPES = [ | |
| null, | |
| [[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 | |
| [[0, 4, 4], [0, 4, 4], [0, 0, 0]], // 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 | |
| ]; | |
| // 游戏状态 | |
| let board = createBoard(ROWS, COLS); | |
| let piece = null; | |
| let nextPiece = null; | |
| let score = 0; | |
| let level = 1; | |
| let gameOver = false; | |
| let isPaused = false; | |
| let dropInterval = null; | |
| let dropSpeed = 1000; // 初始下落速度(ms) | |
| // DOM元素 | |
| const boardElement = document.getElementById('board'); | |
| const nextPieceElement = document.getElementById('nextPiece'); | |
| const scoreElement = document.getElementById('score'); | |
| const levelElement = document.getElementById('level'); | |
| const pauseBtn = document.getElementById('pauseBtn'); | |
| const gameOverlay = document.getElementById('gameOverlay'); | |
| const overlayTitle = document.getElementById('overlayTitle'); | |
| const overlayText = document.getElementById('overlayText'); | |
| const overlayBtn = document.getElementById('overlayBtn'); | |
| const leftBtn = document.getElementById('leftBtn'); | |
| const rightBtn = document.getElementById('rightBtn'); | |
| const downBtn = document.getElementById('downBtn'); | |
| const rotateBtn = document.getElementById('rotateBtn'); | |
| const dropBtn = document.getElementById('dropBtn'); | |
| // 初始化游戏板 | |
| function createBoard(rows, cols) { | |
| return Array.from({ length: rows }, () => Array(cols).fill(0)); | |
| } | |
| // 绘制游戏板 | |
| function drawBoard() { | |
| boardElement.innerHTML = ''; | |
| for (let r = 0; r < ROWS; r++) { | |
| for (let c = 0; c < COLS; c++) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell absolute'; | |
| cell.style.width = `${BLOCK_SIZE}px`; | |
| cell.style.height = `${BLOCK_SIZE}px`; | |
| cell.style.left = `${c * BLOCK_SIZE}px`; | |
| cell.style.top = `${r * BLOCK_SIZE}px`; | |
| if (board[r][c]) { | |
| cell.classList.add('filled'); | |
| cell.style.backgroundColor = COLORS[board[r][c]]; | |
| } else { | |
| cell.style.backgroundColor = 'transparent'; | |
| } | |
| boardElement.appendChild(cell); | |
| } | |
| } | |
| } | |
| // 绘制当前方块 | |
| function drawPiece() { | |
| if (!piece) return; | |
| 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 cell = document.createElement('div'); | |
| cell.className = 'cell absolute filled'; | |
| cell.style.width = `${BLOCK_SIZE}px`; | |
| cell.style.height = `${BLOCK_SIZE}px`; | |
| cell.style.left = `${(piece.col + c) * BLOCK_SIZE}px`; | |
| cell.style.top = `${(piece.row + r) * BLOCK_SIZE}px`; | |
| cell.style.backgroundColor = COLORS[piece.shape[r][c]]; | |
| cell.classList.add(`piece-${String.fromCharCode(96 + piece.shape[r][c])}`); | |
| boardElement.appendChild(cell); | |
| } | |
| } | |
| } | |
| } | |
| // 绘制下一个方块预览 | |
| function drawNextPiece() { | |
| nextPieceElement.innerHTML = ''; | |
| if (!nextPiece) return; | |
| const size = Math.max(nextPiece.shape.length, nextPiece.shape[0].length); | |
| const blockSize = 64 / size; | |
| for (let r = 0; r < nextPiece.shape.length; r++) { | |
| for (let c = 0; c < nextPiece.shape[r].length; c++) { | |
| if (nextPiece.shape[r][c]) { | |
| const cell = document.createElement('div'); | |
| cell.className = 'cell absolute filled'; | |
| cell.style.width = `${blockSize}px`; | |
| cell.style.height = `${blockSize}px`; | |
| cell.style.left = `${c * blockSize}px`; | |
| cell.style.top = `${r * blockSize}px`; | |
| cell.style.backgroundColor = COLORS[nextPiece.shape[r][c]]; | |
| nextPieceElement.appendChild(cell); | |
| } | |
| } | |
| } | |
| } | |
| // 创建新方块 | |
| function createPiece() { | |
| if (!nextPiece) { | |
| // 第一次生成两个方块,一个作为当前,一个作为下一个 | |
| const shapeIndex1 = Math.floor(Math.random() * 7) + 1; | |
| const shapeIndex2 = Math.floor(Math.random() * 7) + 1; | |
| piece = { | |
| shape: SHAPES[shapeIndex1], | |
| row: 0, | |
| col: Math.floor(COLS / 2) - Math.floor(SHAPES[shapeIndex1][0].length / 2), | |
| type: shapeIndex1 | |
| }; | |
| nextPiece = { | |
| shape: SHAPES[shapeIndex2], | |
| type: shapeIndex2 | |
| }; | |
| } else { | |
| // 使用之前生成的下一个方块 | |
| piece = { | |
| shape: nextPiece.shape, | |
| row: 0, | |
| col: Math.floor(COLS / 2) - Math.floor(nextPiece.shape[0].length / 2), | |
| type: nextPiece.type | |
| }; | |
| // 生成新的下一个方块 | |
| const shapeIndex = Math.floor(Math.random() * 7) + 1; | |
| nextPiece = { | |
| shape: SHAPES[shapeIndex], | |
| type: shapeIndex | |
| }; | |
| } | |
| // 检查游戏是否结束 | |
| if (isCollision()) { | |
| gameOver = true; | |
| showOverlay('游戏结束', `最终得分: ${score}`, '重新开始'); | |
| } | |
| drawNextPiece(); | |
| } | |
| // 检查碰撞 | |
| function isCollision(offsetRow = 0, offsetCol = 0, testShape = null) { | |
| const shapeToTest = testShape || piece.shape; | |
| for (let r = 0; r < shapeToTest.length; r++) { | |
| for (let c = 0; c < shapeToTest[r].length; c++) { | |
| if (shapeToTest[r][c]) { | |
| const newRow = piece.row + r + offsetRow; | |
| const newCol = piece.col + c + offsetCol; | |
| if ( | |
| newRow >= ROWS || | |
| newCol < 0 || | |
| newCol >= COLS || | |
| (newRow >= 0 && board[newRow][newCol]) | |
| ) { | |
| return true; | |
| } | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| // 锁定方块到游戏板 | |
| function lockPiece() { | |
| 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 boardRow = piece.row + r; | |
| if (boardRow >= 0) { // 防止在顶部锁定 | |
| board[boardRow][piece.col + c] = piece.shape[r][c]; | |
| } | |
| } | |
| } | |
| } | |
| // 检查是否有完整的行 | |
| checkRows(); | |
| // 创建新方块 | |
| createPiece(); | |
| } | |
| // 检查并清除完整的行 | |
| function checkRows() { | |
| let linesCleared = 0; | |
| for (let r = ROWS - 1; r >= 0; r--) { | |
| if (board[r].every(cell => cell !== 0)) { | |
| // 移除该行 | |
| board.splice(r, 1); | |
| // 在顶部添加新行 | |
| board.unshift(Array(COLS).fill(0)); | |
| linesCleared++; | |
| r++; // 重新检查当前行(现在是新行) | |
| } | |
| } | |
| if (linesCleared > 0) { | |
| // 更新分数 | |
| updateScore(linesCleared); | |
| } | |
| } | |
| // 更新分数 | |
| function updateScore(lines) { | |
| // 计分规则: 1行=100, 2行=300, 3行=500, 4行=800 | |
| const points = [0, 100, 300, 500, 800][lines]; | |
| score += points * level; | |
| // 每1000分升一级 | |
| const newLevel = Math.floor(score / 1000) + 1; | |
| if (newLevel > level) { | |
| level = newLevel; | |
| levelElement.textContent = level; | |
| // 提高速度(最高10级) | |
| dropSpeed = Math.max(100, 1000 - (level - 1) * 100); | |
| resetDropInterval(); | |
| } | |
| scoreElement.textContent = score; | |
| } | |
| // 移动方块 | |
| function movePiece(offsetRow, offsetCol) { | |
| if (gameOver || isPaused) return; | |
| if (!isCollision(offsetRow, offsetCol)) { | |
| piece.row += offsetRow; | |
| piece.col += offsetCol; | |
| draw(); | |
| return true; | |
| } | |
| // 如果是向下移动且发生碰撞,则锁定方块 | |
| if (offsetRow === 1) { | |
| lockPiece(); | |
| draw(); | |
| } | |
| return false; | |
| } | |
| // 旋转方块 | |
| function rotatePiece() { | |
| if (gameOver || isPaused) return; | |
| // 创建旋转后的形状 | |
| const newShape = []; | |
| for (let c = 0; c < piece.shape[0].length; c++) { | |
| const newRow = []; | |
| for (let r = piece.shape.length - 1; r >= 0; r--) { | |
| newRow.push(piece.shape[r][c]); | |
| } | |
| newShape.push(newRow); | |
| } | |
| // 检查旋转后是否会碰撞 | |
| if (!isCollision(0, 0, newShape)) { | |
| piece.shape = newShape; | |
| draw(); | |
| } else { | |
| // 尝试墙踢(左右移动一格再旋转) | |
| if (!isCollision(0, -1, newShape)) { | |
| piece.shape = newShape; | |
| piece.col -= 1; | |
| draw(); | |
| } else if (!isCollision(0, 1, newShape)) { | |
| piece.shape = newShape; | |
| piece.col += 1; | |
| draw(); | |
| } | |
| } | |
| } | |
| // 硬降(立即下落) | |
| function hardDrop() { | |
| if (gameOver || isPaused) return; | |
| while (movePiece(1, 0)) { | |
| // 持续下落直到不能移动 | |
| } | |
| } | |
| // 绘制游戏 | |
| function draw() { | |
| drawBoard(); | |
| drawPiece(); | |
| } | |
| // 下落方块 | |
| function dropPiece() { | |
| movePiece(1, 0); | |
| } | |
| // 重置下落间隔 | |
| function resetDropInterval() { | |
| if (dropInterval) { | |
| clearInterval(dropInterval); | |
| } | |
| dropInterval = setInterval(dropPiece, dropSpeed); | |
| } | |
| // 显示覆盖层 | |
| function showOverlay(title, text, btnText) { | |
| overlayTitle.textContent = title; | |
| overlayText.textContent = text; | |
| overlayBtn.textContent = btnText; | |
| gameOverlay.classList.add('active'); | |
| } | |
| // 隐藏覆盖层 | |
| function hideOverlay() { | |
| gameOverlay.classList.remove('active'); | |
| } | |
| // 开始新游戏 | |
| function startGame() { | |
| board = createBoard(ROWS, COLS); | |
| score = 0; | |
| level = 1; | |
| gameOver = false; | |
| isPaused = false; | |
| dropSpeed = 1000; | |
| scoreElement.textContent = score; | |
| levelElement.textContent = level; | |
| createPiece(); | |
| draw(); | |
| resetDropInterval(); | |
| hideOverlay(); | |
| } | |
| // 暂停/继续游戏 | |
| function togglePause() { | |
| if (gameOver) return; | |
| isPaused = !isPaused; | |
| if (isPaused) { | |
| clearInterval(dropInterval); | |
| showOverlay('游戏暂停', '点击继续按钮返回游戏', '继续游戏'); | |
| } else { | |
| resetDropInterval(); | |
| hideOverlay(); | |
| } | |
| // 更新暂停按钮图标 | |
| pauseBtn.innerHTML = isPaused ? '<i class="fas fa-play text-gray-700"></i>' : '<i class="fas fa-pause text-gray-700"></i>'; | |
| } | |
| // 键盘控制 | |
| function handleKeyDown(e) { | |
| if (gameOver && e.key === ' ') { | |
| startGame(); | |
| return; | |
| } | |
| if (isPaused && e.key === 'p') { | |
| togglePause(); | |
| return; | |
| } | |
| if (gameOver || isPaused) return; | |
| switch (e.key) { | |
| case 'ArrowLeft': | |
| movePiece(0, -1); | |
| break; | |
| case 'ArrowRight': | |
| movePiece(0, 1); | |
| break; | |
| case 'ArrowDown': | |
| movePiece(1, 0); | |
| break; | |
| case 'ArrowUp': | |
| rotatePiece(); | |
| break; | |
| case ' ': | |
| hardDrop(); | |
| break; | |
| } | |
| } | |
| // 触摸控制 | |
| let touchStartX = 0; | |
| let touchStartY = 0; | |
| function handleTouchStart(e) { | |
| touchStartX = e.touches[0].clientX; | |
| touchStartY = e.touches[0].clientY; | |
| } | |
| function handleTouchEnd(e) { | |
| if (gameOver || isPaused) return; | |
| const touchEndX = e.changedTouches[0].clientX; | |
| const touchEndY = e.changedTouches[0].clientY; | |
| const diffX = touchEndX - touchStartX; | |
| const diffY = touchEndY - touchStartY; | |
| // 水平滑动距离大于垂直滑动距离时,认为是左右移动 | |
| if (Math.abs(diffX) > Math.abs(diffY)) { | |
| if (diffX > 0) { | |
| movePiece(0, 1); // 右 | |
| } else { | |
| movePiece(0, -1); // 左 | |
| } | |
| } else { | |
| if (diffY > 0) { | |
| movePiece(1, 0); // 下 | |
| } else { | |
| rotatePiece(); // 上(旋转) | |
| } | |
| } | |
| } | |
| // 事件监听 | |
| document.addEventListener('keydown', handleKeyDown); | |
| boardElement.addEventListener('touchstart', handleTouchStart, { passive: true }); | |
| boardElement.addEventListener('touchend', handleTouchEnd, { passive: true }); | |
| leftBtn.addEventListener('click', () => movePiece(0, -1)); | |
| rightBtn.addEventListener('click', () => movePiece(0, 1)); | |
| downBtn.addEventListener('click', () => movePiece(1, 0)); | |
| rotateBtn.addEventListener('click', rotatePiece); | |
| dropBtn.addEventListener('click', hardDrop); | |
| pauseBtn.addEventListener('click', togglePause); | |
| overlayBtn.addEventListener('click', () => { | |
| if (gameOver) { | |
| startGame(); | |
| } else { | |
| togglePause(); | |
| } | |
| }); | |
| // 开始游戏 | |
| startGame(); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=alex080907/test" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |