test / index.html
alex080907's picture
Add 2 files
bb7f78f verified
Raw History Blame Contribute Delete
27.2 kB
<!DOCTYPE html>
<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>