继之前一款经典俄罗斯方块,近日使用DeepSeek打磨出来一款国际版(已经在https://amitofo.itch.io上线)。此处同与国内的家人朋友们分享:
俄罗斯方块(国际版)

🎮 核心游戏功能
俄罗斯方块经典玩法
游戏状态控制 – 开始/暂停游戏、重置游戏、计分系统
难度递增 – 方块下落速度加快
🎯 操作方式
-
键盘控制:方向键移动,上键旋转,空格键硬降
-
手机触控:底部D-pad方向按钮,中心旋转按钮
-
音效系统:背景音乐、移动音效、消除音效、花瓣特效音
🎨 视觉特效
花瓣飘落效果 – 消除2行以上时触发花瓣动画
动态背景 – 半透明背景图 + 渐变遮罩
响应式布局 – 适配手机/平板/电脑,支持刘海屏安全区域
游戏结束特效 – 普通通关和完美通关不同动画效果
🔊 音频系统
-
背景音乐(beijing.ogg)
-
移动/旋转/下落音效(ai.mp3)
-
消除音效(win.mp3)
-
花瓣特效音(xiaochu.mp3)
📱 移动端优化
-
防误触设计(tap highlight透明)
-
手势支持(滑动/长按连续移动)
-
安全区域适配(刘海屏)
-
横屏布局自适应
🔗 附加功能
-
更多游戏链接 – 点击"MORE"按钮跳转到游戏中心
-
游戏采用纯HTML/CSS/JavaScript实现,无需外部依赖,所有资源通过CDN加载,适合作为移动端H5游戏或嵌入WebView使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Tetris</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* —– reset & safe area —– */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
user-select: none;
touch-action: manipulation;
}
:root {
–safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
–safe-area-inset-top: env(safe-area-inset-top, 0px);
–safe-area-inset-left: env(safe-area-inset-left, 0px);
–safe-area-inset-right: env(safe-area-inset-right, 0px);
/* increased top/bottom padding for more breathing space */
–extra-top-padding: 12px;
–extra-bottom-padding: 18px;
}
body {
font-family: 'Arial', sans-serif;
background: linear-gradient(135deg, rgba(44, 62, 80, 0.75) 0%, rgba(52, 73, 94, 0.75) 100%),
url('https://amitofoicu.github.io/home/lianchi6.jpg') center center fixed;
background-size: cover;
color: #fff;
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
/* extra top/bottom padding added to safe-area values */
padding: max(calc(15px + var(–extra-top-padding)), env(safe-area-inset-top))
max(10px, env(safe-area-inset-right))
max(calc(15px + var(–extra-bottom-padding)), env(safe-area-inset-bottom))
max(10px, env(safe-area-inset-left));
position: relative;
overscroll-behavior: contain;
-webkit-text-size-adjust: 100%;
}
/* —– floating poem (unchanged, but kept for atmosphere) —– */
.poem-container {
position: absolute;
bottom: max(20px, env(safe-area-inset-bottom));
left: max(20px, env(safe-area-inset-left));
text-align: left;
z-index: 1;
opacity: 0.7;
transition: opacity 0.3s;
max-width: 260px;
}
.poem-container:hover { opacity: 1; }
.poem-content {
font-size: 12px;
line-height: 1.6;
color: rgba(255,255,255,0.9);
font-style: italic;
text-shadow: 0 1px 3px rgba(0,0,0,0.7);
padding: 10px;
background: rgba(0,0,0,0.5);
border-radius: 8px;
backdrop-filter: blur(5px);
border: 1px solid rgba(255,255,255,0.2);
}
.poem-title {
font-size: 10px;
color: #ffcc00;
margin-bottom: 5px;
letter-spacing: 1px;
}
/* —– flower container —– */
.flower-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
overflow: hidden;
}
.flower {
position: absolute;
width: 20px;
height: 20px;
pointer-events: none;
z-index: 9999;
opacity: 0;
background-size: contain;
background-repeat: no-repeat;
}
/* —– header (logo, title, more) —– */
.game-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: 400px;
margin-top: 5px;
margin-bottom: 18px; /* increased bottom margin for extra top padding feel */
padding: 0 8px;
z-index: 10;
position: relative;
}
.header-left {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.site-logo {
width: 40px;
height: 40px;
border-radius: 8px;
border: 2px solid #FFD700;
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
object-fit: cover;
background: white;
flex-shrink: 0;
}
.game-title {
font-size: clamp(20px, 5vw, 26px);
color: #ffcc00;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
white-space: nowrap;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-weight: bold;
line-height: 1.2;
}
.more-games-btn {
background: rgba(255, 105, 180, 0.85);
padding: 8px 18px;
border-radius: 30px;
border: 2px solid #FF1493;
box-shadow: 0 0 15px rgba(255, 20, 147, 0.6);
backdrop-filter: blur(5px);
color: #FFFFFF;
text-decoration: none;
font-size: 14px;
font-weight: bold;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
animation: pulse 2s infinite;
white-space: nowrap;
flex-shrink: 0;
}
@keyframes pulse {
0% { box-shadow: 0 0 10px rgba(255, 20, 147, 0.5); }
50% { box-shadow: 0 0 20px rgba(255, 20, 147, 0.8); }
100% { box-shadow: 0 0 10px rgba(255, 20, 147, 0.5); }
}
/* —– main game area with extra spacing —– */
.game-container {
display: flex;
flex-direction: column;
align-items: center;
max-width: 100%;
width: 100%;
height: 100%;
max-height: 100%;
z-index: 2;
margin-top: 0;
}
.game-area {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex: 1;
min-height: 0;
overflow: hidden;
}
.game-board-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 400px;
flex: 1;
min-height: 0;
}
/* —– next row with spacing —– */
.next-row {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 12px; /* extra spacing */
flex-shrink: 0;
gap: 8px;
background: rgba(0,0,0,0.3);
padding: 8px 12px; /* increased vertical padding */
border-radius: 50px;
backdrop-filter: blur(8px);
border: 1px solid rgba(255,255,255,0.15);
}
.next-piece-label {
font-size: clamp(12px, 3vw, 14px);
color: #ffffff;
font-weight: bold;
white-space: nowrap;
display: flex;
align-items: center;
gap: 6px;
}
.next-piece-canvas {
background: rgba(255,255,255,0.9);
border-radius: 8px;
border: 2px solid #4CAF50;
width: 60px;
height: 60px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.control-group {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
}
.score-badge {
display: flex;
align-items: center;
gap: 6px;
background: rgba(255,215,0,0.2);
padding: 4px 14px 4px 10px;
border-radius: 30px;
border: 1.5px solid #FFD700;
box-shadow: 0 0 10px rgba(255,215,0,0.4);
margin-right: 2px;
font-weight: bold;
}
.score-badge i {
color: #FFD700;
font-size: 16px;
}
.score-badge .score-value {
font-size: 22px;
font-weight: 800;
color: #fff;
text-shadow: 0 0 8px #FFD700;
min-width: 50px;
text-align: center;
line-height: 1;
}
.mini-control-btn {
background: linear-gradient(145deg, #4CAF50, #45a049);
color: white;
border: none;
border-radius: 30px;
padding: 8px 14px; /* slightly bigger touch target */
font-size: clamp(12px, 3vw, 14px);
font-weight: bold;
cursor: pointer;
box-shadow: 0 3px 0 #2e7d32, 0 4px 8px rgba(0,0,0,0.2);
transition: all 0.1s;
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.mini-control-btn:active {
transform: translateY(3px);
box-shadow: 0 1px 0 #2e7d32;
}
.mini-control-btn.pause-style {
background: linear-gradient(145deg, #FF9800, #F57C00);
box-shadow: 0 3px 0 #b26a00;
}
.sound-toggle-btn {
background: linear-gradient(145deg, #2196F3, #1976D2);
border: none;
border-radius: 50%;
width: 44px; /* slightly larger */
height: 44px;
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 3px 0 #0b5e9e;
transition: 0.1s;
flex-shrink: 0;
}
.sound-toggle-btn:active {
transform: scale(0.95);
}
/* —– canvas container with extra bottom hint spacing —– */
.main-game {
display: flex;
justify-content: center;
width: 100%;
flex: 1;
min-height: 0;
}
.game-canvas-container {
position: relative;
margin: 0 5px;
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-height: 70vh;
width: 100%;
}
#gameCanvas {
display: block;
background: url('https://amitofoicu.github.io/home/lianchi6.jpg') center center / cover no-repeat, #ffffff;
border-radius: 12px;
border: 4px solid #ffcc00;
box-shadow: 0 0 25px rgba(255,204,0,0.4), 0 8px 20px rgba(0,0,0,0.3);
width: 100%;
height: 100%;
max-width: 300px;
max-height: 600px;
object-fit: contain;
outline: none;
image-rendering: crisp-edges;
}
/* keyboard hint with more margin */
.keyboard-hint {
margin-top: 14px; /* increased top margin */
color: rgba(255,255,255,0.7);
font-size: 14px;
background: rgba(0,0,0,0.3);
padding: 8px 22px;
border-radius: 40px;
backdrop-filter: blur(5px);
border: 1px solid rgba(255,255,255,0.2);
letter-spacing: 0.5px;
font-weight: 400;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
text-align: center;
white-space: nowrap;
}
.keyboard-hint i {
color: #FFD700;
margin: 0 4px;
font-size: 16px;
}
/* —– bottom controls with extra bottom padding (already increased in body) —– */
.controls-container {
margin-top: 16px; /* more spacing from canvas */
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
flex-shrink: 0;
/* extra bottom padding handled by body */
}
.mobile-controls {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
margin-top: 8px;
}
.dpad-controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr) 5px;
gap: 10px; /* increased gap for bigger touch area */
width: clamp(160px, 42vw, 210px);
height: clamp(110px, 28vw, 140px);
margin: 0 auto;
}
.dpad-button {
background: linear-gradient(145deg, #5C6BC0, #3F51B5);
border: none;
border-radius: 18px; /* slightly more rounded */
color: white;
font-size: clamp(22px, 5.5vw, 28px);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.1s;
box-shadow: 0 4px 0 #283593, 0 6px 12px rgba(0,0,0,0.2);
outline: none;
-webkit-tap-highlight-color: transparent;
}
.dpad-button:active {
background: linear-gradient(145deg, #7986CB, #5C6BC0);
transform: scale(0.95);
box-shadow: 0 2px 0 #283593, 0 4px 8px rgba(0,0,0,0.2);
}
.dpad-button:focus {
outline: none;
}
.dpad-button.left { grid-column: 1; grid-row: 1; }
.dpad-button.center { grid-column: 2; grid-row: 1; background: linear-gradient(145deg, #9C27B0, #7B1FA2); box-shadow: 0 4px 0 #4A148C; }
.dpad-button.right { grid-column: 3; grid-row: 1; }
.dpad-button.down { grid-column: 2; grid-row: 2; }
.dpad-spacer {
grid-column: 2;
grid-row: 3;
height: 5px;
pointer-events: none;
}
/* game overlays */
.game-over, .perfect-game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 12px;
z-index: 10;
padding: 20px;
border: 4px solid #ff3333;
}
.perfect-game-over {
background: rgba(0,0,0,0.95);
border: 4px solid #00ff00;
animation: perfectGlow 2s infinite alternate;
z-index: 11;
}
@keyframes perfectGlow {
0% { box-shadow: 0 0 20px rgba(0,255,0,0.5); border-color: #00ff00; }
100% { box-shadow: 0 0 40px rgba(0,255,0,0.8); border-color: #00ffcc; }
}
.infinity-symbol {
font-size: clamp(36px,8vw,48px);
color: #00ff00;
text-shadow: 0 0 20px rgba(0,255,0,0.9);
margin: 15px 0;
font-weight: bold;
animation: infinityGlow 2s infinite alternate;
}
@keyframes infinityGlow {
0% { text-shadow: 0 0 10px #0f0, 0 0 20px #0f0; transform: scale(1); }
50% { text-shadow: 0 0 15px #0f0, 0 0 30px #0f0, 0 0 45px #0f0; transform: scale(1.1); }
100% { text-shadow: 0 0 20px #0f0, 0 0 40px #0f0; transform: scale(1.05); }
}
/* iPhone optimizations */
@media (max-width: 768px) {
.keyboard-hint {
display: none;
}
.controls-container {
margin-bottom: 5px;
}
#gameCanvas {
border-width: 4px;
max-width: calc(100% – 8px);
margin: 0 auto;
}
.game-canvas-container {
margin: 0;
padding: 0;
width: 100%;
}
.game-header {
margin-top: 10px;
margin-bottom: 18px;
}
.controls-container {
margin-top: 20px;
}
}
@supports (padding: max(0px)) {
body {
padding-left: max(10px, env(safe-area-inset-left));
padding-right: max(10px, env(safe-area-inset-right));
padding-top: max(calc(15px + var(–extra-top-padding)), env(safe-area-inset-top));
padding-bottom: max(calc(15px + var(–extra-bottom-padding)), env(safe-area-inset-bottom));
}
.poem-container { left: max(20px, env(safe-area-inset-left)); bottom: max(20px, env(safe-area-inset-bottom)); }
}
/* landscape tiny adjustment */
@media (orientation: landscape) and (max-height: 600px) {
.game-container { flex-direction: row; justify-content: space-between; }
.game-header { max-width: 20%; flex-direction: column; align-items: flex-start; margin-top: 10px; }
.game-area { flex-direction: row; width: auto; flex: 1; }
.game-board-container { flex-direction: row; max-width: none; }
.next-row { flex-direction: column; width: auto; margin-right: 10px; }
.main-game { flex: 1; height: 100%; }
.controls-container { margin-top: 0; margin-left: 10px; width: auto; max-width: 30%; }
}
</style>
</head>
<body>
<!– floating petals (same) –>
<div class="flower-container" id="flowerContainer"></div>
<!– header with english title –>
<div class="game-header">
<div class="header-left">
<img src="https://amitofoicu.github.io/home/logo.jpg" alt="logo" class="site-logo" onerror="this.style.display='none'">
</div>
<span class="game-title"><i class="fas fa-crown" style="margin-right: 4px;"></i>TETRIS</span>
<a href="https://amitofoicu.github.io/home/main.html" class="more-games-btn">
<i class=""></i> MORE
</a>
</div>
<!– main container –>
<div class="game-container">
<div class="game-area">
<div class="game-board-container">
<!– next row with score / controls –>
<div class="next-row">
<div class="next-piece-label">
<i class=""></i> NEXT
<canvas id="nextPieceCanvas" class="next-piece-canvas" width="90" height="90"></canvas>
</div>
<div class="control-group">
<div class="score-badge">
<i class="fas fa-star"></i>
<span class="score-value" id="score">0</span>
</div>
<button class="mini-control-btn" id="startPauseBtn"><i class="fas fa-play"></i> START</button>
<button class="sound-toggle-btn" id="soundToggle"><i class="fas fa-volume-up"></i></button>
</div>
</div>
<!– main canvas + keyboard hint –>
<div class="main-game">
<div class="game-canvas-container">
<canvas id="gameCanvas" width="190" height="380"></canvas>
<div class="keyboard-hint">
<i class="fas fa-arrow-left"></i>
<i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-right"></i>
<i class="fas fa-arrow-down"></i> swipe / keys
</div>
<!– game over overlays –>
<div class="game-over" id="gameOver" style="display: none;">
<h2>🎉 CONGRATS! 🎉</h2>
<p>SCORE: <span id="finalScore">0</span></p>
<button class="mini-control-btn" id="restartBtn">PLAY AGAIN</button>
</div>
<div class="perfect-game-over" id="perfectGameOver" style="display: none;">
<h2>🎉 CONGRATS! 🎉</h2>
<p>PERFECT · SCORE:</p>
<div class="infinity-symbol">∞</div>
<button class="mini-control-btn" style="background:linear-gradient(145deg,#9C27B0,#7B1FA2)" id="zenDecryptBtn">
<i class="fas fa-lock-open"></i> ZEN DECRYPT
</button>
</div>
</div>
</div>
</div>
<!– bottom dpad –>
<div class="controls-container">
<div class="mobile-controls">
<div class="dpad-controls">
<button class="dpad-button left" id="leftBtn"><i class="fas fa-arrow-left"></i></button>
<button class="dpad-button center" id="centerBtn"><i class="fas fa-redo-alt"></i></button>
<button class="dpad-button right" id="rightBtn"><i class="fas fa-arrow-right"></i></button>
<button class="dpad-button down" id="downBtn"><i class="fas fa-arrow-down"></i></button>
<div class="dpad-spacer"></div>
</div>
</div>
</div>
</div>
</div>
<!– audio elements (same sources) –>
<audio id="backgroundMusic" loop preload="auto"><source src="https://amitofoicu.github.io/home/beijing.ogg" type="audio/ogg"></audio>
<audio id="clearSound" preload="auto"><source src="https://amitofoicu.github.io/home/win.mp3" type="audio/mpeg"></audio>
<audio id="moveSound" preload="auto"><source src="https://amitofoicu.github.io/home/ai.mp3" type="audio/mpeg"></audio>
<audio id="rotateSound" preload="auto"><source src="https://amitofoicu.github.io/home/ai.mp3" type="audio/mpeg"></audio>
<audio id="dropSound" preload="auto"><source src="https://amitofoicu.github.io/home/ai.mp3" type="audio/mpeg"></audio>
<audio id="perfectSound" preload="auto"><source src="https://amitofoicu.github.io/home/win.mp3" type="audio/mpeg"></audio>
<audio id="flowerSound" preload="auto"><source src="https://amitofoicu.github.io/home/xiaochu.mp3" type="audio/mpeg"></audio>
<script>
// ———- CORE LOGIC ———-
const COLS = 10, ROWS = 20;
const COLORS = ['#FFFFFF','#00D8FF','#0066FF','#FF8000','#FFD700','#00CC00','#AA00FF','#FF3333'];
const SHAPES = [
[[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],
[[2,0,0],[2,2,2],[0,0,0]],
[[0,0,3],[3,3,3],[0,0,0]],
[[4,4],[4,4]],
[[0,5,5],[5,5,0],[0,0,0]],
[[0,6,0],[6,6,6],[0,0,0]],
[[7,7,0],[0,7,7],[0,0,0]]
];
let board = [], currentPiece = null, nextPiece = null, pieceRow = 0, pieceCol = 0;
let score = 0, level = 1, lines = 0, gameOver = false, isPlaying = false, isPaused = false;
let dropCounter = 0, dropInterval = 1000, lastTime = 0, blockSize = 25, soundEnabled = true;
let musicPaused = false, audioInitialized = false;
let isPerfectGame = true, hasRotated = false, hasAccelerated = false, hasChangedSpeed = false;
let initialSpeed = 1000, totalPieces = 0;
// DOM elements
const gameCanvas = document.getElementById('gameCanvas');
const nextPieceCanvas = document.getElementById('nextPieceCanvas');
const ctx = gameCanvas.getContext('2d');
const nextCtx = nextPieceCanvas.getContext('2d');
const scoreElement = document.getElementById('score');
const gameOverElement = document.getElementById('gameOver');
const perfectGameOverElement = document.getElementById('perfectGameOver');
const finalScoreElement = document.getElementById('finalScore');
const flowerContainer = document.getElementById('flowerContainer');
const startPauseBtn = document.getElementById('startPauseBtn');
const soundToggle = document.getElementById('soundToggle');
const restartBtn = document.getElementById('restartBtn');
const zenDecryptBtn = document.getElementById('zenDecryptBtn');
const leftBtn = document.getElementById('leftBtn');
const rightBtn = document.getElementById('rightBtn');
const downBtn = document.getElementById('downBtn');
const centerBtn = document.getElementById('centerBtn');
// audio
const backgroundMusic = document.getElementById('backgroundMusic');
const clearSound = document.getElementById('clearSound');
const moveSound = document.getElementById('moveSound');
const rotateSound = document.getElementById('rotateSound');
const dropSound = document.getElementById('dropSound');
const perfectSound = document.getElementById('perfectSound');
const flowerSound = document.getElementById('flowerSound');
// preload all audio
function preloadAllAudio() {
const allAudio = [backgroundMusic, clearSound, moveSound, rotateSound, dropSound, perfectSound, flowerSound];
allAudio.forEach(audio => {
audio.load();
audio.volume = 0.01;
audio.play().then(() => {
audio.pause();
audio.currentTime = 0;
// Set final volumes after preload
if (audio.id === 'backgroundMusic') audio.volume = 0.5;
else if (audio.id === 'moveSound' || audio.id === 'rotateSound' || audio.id === 'dropSound') audio.volume = 0.1;
else if (audio.id === 'clearSound') audio.volume = 0.1;
else if (audio.id === 'perfectSound') audio.volume = 0.7;
else if (audio.id === 'flowerSound') audio.volume = 0.6;
}).catch(() => {
// Set volumes even if autoplay fails
if (audio.id === 'backgroundMusic') audio.volume = 0.5;
else if (audio.id === 'moveSound' || audio.id === 'rotateSound' || audio.id === 'dropSound') audio.volume = 0.1;
else if (audio.id === 'clearSound') audio.volume = 0.1;
else if (audio.id === 'perfectSound') audio.volume = 0.7;
else if (audio.id === 'flowerSound') audio.volume = 0.6;
});
});
}
function initAudio() {
if (audioInitialized) return;
// Set all volumes to desired levels
backgroundMusic.volume = 0.5; //背景音乐
clearSound.volume = 0.1; //消除音效 (win.mp3)
moveSound.volume = 0.1; //移动音效 (ai.mp3) – LOW VOLUME
rotateSound.volume = 0.1; //旋转音效 (ai.mp3) – LOW VOLUME
dropSound.volume = 0.1; //下落音效 (ai.mp3) – LOW VOLUME
perfectSound.volume = 0.7; //完美通关音效 (win.mp3)
flowerSound.volume = 0.6; //花瓣音效 (xiaochu.mp3)
[backgroundMusic, clearSound, moveSound, rotateSound, dropSound, perfectSound, flowerSound].forEach(a => {
a.addEventListener('error', e => console.log(`Audio load error: ${a.id}`));
});
backgroundMusic.loop = true;
audioInitialized = true;
preloadAllAudio();
}
function playSound(sound) { if (!soundEnabled) return; try { sound.currentTime = 0; sound.play().catch(()=>{}); } catch(e){} }
function playBackgroundMusic() { if (!soundEnabled || !isPlaying || isPaused) return; try { backgroundMusic.play(); musicPaused = false; } catch(e){} }
function pauseBackgroundMusic() { try { backgroundMusic.pause(); musicPaused = true; } catch(e){} }
function toggleSound() {
if (soundEnabled) {
soundEnabled = false;
pauseBackgroundMusic();
soundToggle.innerHTML = '<i class="fas fa-volume-mute"></i>';
soundToggle.style.background = 'linear-gradient(145deg, #666, #444)';
} else {
soundEnabled = true;
soundToggle.innerHTML = '<i class="fas fa-volume-up"></i>';
soundToggle.style.background = 'linear-gradient(145deg, #2196F3, #1976D2)';
if (isPlaying && !isPaused) playBackgroundMusic();
}
}
// flower effect
function createFlowerEffect(linesCleared) {
if (!soundEnabled) return;
playSound(flowerSound);
let flowerCount = linesCleared === 2 ? 80 : (linesCleared >= 3 ? 150 : 50);
flowerContainer.innerHTML = '';
for (let i=0; i<flowerCount; i++) {
const flower = document.createElement('div');
flower.classList.add('flower');
const types = ['🌸','🌺','🌼','🌻','🌷','💮','🏵️','🥀','🌹','🌞'];
flower.textContent = types[Math.floor(Math.random()*types.length)];
flower.style.fontSize = `${Math.random()*20+15}px`;
const colors = ['#FF6B6B','#4ECDC4','#FFD166','#06D6A0','#118AB2','#EF476F'];
flower.style.color = colors[Math.floor(Math.random()*colors.length)];
const startX = Math.random()*window.innerWidth;
flower.style.left = `${startX}px`; flower.style.top = `-30px`;
const endY = window.innerHeight+30;
const duration = Math.random()*3+3;
let start = null;
function animate(ts) {
if(!start) start=ts;
const prog = Math.min((ts-start)/(duration*1000),1);
const y = -30 + (endY+30)*prog;
const swing = Math.sin(prog*Math.PI*5)*20;
flower.style.left = `${startX+swing}px`;
flower.style.top = `${y}px`;
flower.style.opacity = prog<0.2 ? prog/0.2 : (prog>0.8 ? 1-(prog-0.8)/0.2 : 1);
if(prog<1) requestAnimationFrame(animate);
else flower.remove();
}
setTimeout(()=>requestAnimationFrame(animate), Math.random()*800);
flowerContainer.appendChild(flower);
}
}
function init() {
board = Array.from({length:ROWS},()=>Array(COLS).fill(0));
score=0; level=1; lines=0; gameOver=false; isPlaying=false; isPaused=false; dropInterval=1000;
isPerfectGame=true; hasRotated=false; hasAccelerated=false; hasChangedSpeed=false; initialSpeed=1000; totalPieces=0;
currentPiece = createRandomPiece(); nextPiece = createRandomPiece();
updateDisplay(); drawNextPiece();
gameOverElement.style.display='none'; perfectGameOverElement.style.display='none';
flowerContainer.innerHTML=''; pauseBackgroundMusic();
resizeCanvas();
startPauseBtn.innerHTML = '<i class="fas fa-play"></i> START';
startPauseBtn.classList.remove('pause-style');
}
function createRandomPiece() { let t=Math.floor(Math.random()*SHAPES.length); return {shape:SHAPES[t], color:COLORS[t+1], type:t}; }
function drawBlock(ctx,x,y,color){
ctx.fillStyle=color; ctx.fillRect(x*blockSize,y*blockSize,blockSize,blockSize);
ctx.strokeStyle='#000'; ctx.lineWidth=2; ctx.strokeRect(x*blockSize,y*blockSize,blockSize,blockSize);
ctx.fillStyle='rgba(255,255,255,0.4)'; ctx.fillRect(x*blockSize,y*blockSize,blockSize-2,4); ctx.fillRect(x*blockSize,y*blockSize,4,blockSize-2);
ctx.fillStyle='rgba(0,0,0,0.3)'; ctx.fillRect(x*blockSize+2,y*blockSize+blockSize-4,blockSize-2,4); ctx.fillRect(x*blockSize+blockSize-4,y*blockSize+2,4,blockSize-2);
}
function drawBoard(){
ctx.clearRect(0,0,gameCanvas.width,gameCanvas.height);
ctx.strokeStyle='rgba(0,0,0,0.1)'; ctx.lineWidth=1;
for(let x=0;x<=COLS;x++){ ctx.beginPath(); ctx.moveTo(x*blockSize,0); ctx.lineTo(x*blockSize,ROWS*blockSize); ctx.stroke(); }
for(let y=0;y<=ROWS;y++){ ctx.beginPath(); ctx.moveTo(0,y*blockSize); ctx.lineTo(COLS*blockSize,y*blockSize); ctx.stroke(); }
for(let y=0;y<ROWS;y++) for(let x=0;x<COLS;x++) if(board[y][x]) drawBlock(ctx,x,y,COLORS[board[y][x]]);
if(currentPiece) for(let y=0;y<currentPiece.shape.length;y++) for(let x=0;x<currentPiece.shape[y].length;x++) if(currentPiece.shape[y][x]) { let bx=pieceCol+x, by=pieceRow+y; if(by>=0) drawBlock(ctx,bx,by,currentPiece.color); }
}
function drawNextPiece(){
nextCtx.clearRect(0,0,nextPieceCanvas.width,nextPieceCanvas.height);
nextCtx.fillStyle='#FFFFFF'; nextCtx.fillRect(0,0,nextPieceCanvas.width,nextPieceCanvas.height);
if(!nextPiece) return;
let b=25, offX=(nextPieceCanvas.width-nextPiece.shape[0].length*b)/2, offY=(nextPieceCanvas.height-nextPiece.shape.length*b)/2;
for(let y=0;y<nextPiece.shape.length;y++) for(let x=0;x<nextPiece.shape[y].length;x++) if(nextPiece.shape[y][x]){
nextCtx.fillStyle=nextPiece.color; nextCtx.fillRect(offX+x*b,offY+y*b,b,b);
nextCtx.strokeStyle='#000'; nextCtx.lineWidth=2; nextCtx.strokeRect(offX+x*b,offY+y*b,b,b);
nextCtx.fillStyle='rgba(255,255,255,0.4)'; nextCtx.fillRect(offX+x*b,offY+y*b,b-2,3); nextCtx.fillRect(offX+x*b,offY+y*b,3,b-2);
}
nextCtx.strokeStyle='#4CAF50'; nextCtx.lineWidth=3; nextCtx.strokeRect(0,0,nextPieceCanvas.width,nextPieceCanvas.height);
}
function checkCollision(piece,row,col){ for(let y=0;y<piece.shape.length;y++) for(let x=0;x<piece.shape[y].length;x++) if(piece.shape[y][x]){ let nx=col+x, ny=row+y; if(nx<0||nx>=COLS||ny>=ROWS||(ny>=0&&board[ny][nx])) return true; } return false; }
function mergePiece(){ for(let y=0;y<currentPiece.shape.length;y++) for(let x=0;x<currentPiece.shape[y].length;x++) if(currentPiece.shape[y][x]) { let by=pieceRow+y, bx=pieceCol+x; if(by>=0) board[by][bx]=currentPiece.shape[y][x]; } playSound(dropSound); totalPieces++; }
function clearLines() {
let cleared=0;
for(let y=ROWS-1;y>=0;y–){
let full=true; for(let x=0;x<COLS;x++) if(!board[y][x]){ full=false; break; }
if(full){
cleared++;
for(let yy=y;yy>0;yy–) for(let x=0;x<COLS;x++) board[yy][x]=board[yy-1][x];
for(let x=0;x<COLS;x++) board[0][x]=0;
y++;
}
}
if(cleared>0){
playSound(clearSound);
if(cleared>=2) createFlowerEffect(cleared);
lines += cleared;
let pts=[0,100,300,500,800]; score += pts[cleared]*level;
let newLevel = Math.floor(lines/10)+1;
if(newLevel>level){
level=newLevel;
let newSpeed = Math.max(100,1000-(level-1)*100);
if(newSpeed!==dropInterval){ hasChangedSpeed=true; if(isPlaying) isPerfectGame=false; }
dropInterval=newSpeed;
}
updateDisplay();
}
return cleared;
}
function rotatePiece(){
if(!currentPiece||isPaused||!isPlaying) return;
hasRotated=true; if(isPlaying) isPerfectGame=false;
let shape=currentPiece.shape, rows=shape.length, cols=shape[0].length;
let rotated=Array.from({length:cols},()=>Array(rows).fill(0));
for(let y=0;y<rows;y++) for(let x=0;x<cols;x++) rotated[x][rows-1-y]=shape[y][x];
let original=currentPiece.shape; currentPiece.shape=rotated;
if(checkCollision(currentPiece,pieceRow,pieceCol)) {
currentPiece.shape=original;
playSound(moveSound); //碰撞时播放移动音效 (ai.mp3)
} else {
playSound(rotateSound); //成功旋转播放旋转音效 (ai.mp3)
}
drawBoard();
}
function movePiece(dx,dy){
if(!currentPiece||isPaused||!isPlaying) return false;
if(dy>0){ hasAccelerated=true; if(isPlaying) isPerfectGame=false; }
let newRow=pieceRow+dy, newCol=pieceCol+dx;
if(!checkCollision(currentPiece,newRow,newCol)){
pieceRow=newRow; pieceCol=newCol; drawBoard();
if(dx!==0) playSound(moveSound); //左右移动播放移动音效 (ai.mp3)
return true;
}
if(dy>0){ mergePiece(); clearLines(); spawnNewPiece(); }
return false;
}
function hardDrop() {
if(!currentPiece||isPaused||!isPlaying) return;
hasAccelerated = true;
isPerfectGame = false;
while(!checkCollision(currentPiece, pieceRow+1, pieceCol)) {
pieceRow++;
}
mergePiece(); //mergePiece内部会播放dropSound (ai.mp3)
clearLines();
spawnNewPiece();
drawBoard();
}
function spawnNewPiece(){
currentPiece=nextPiece; nextPiece=createRandomPiece(); pieceRow=0; pieceCol=Math.floor(COLS/2)-Math.floor(currentPiece.shape[0].length/2);
if(checkCollision(currentPiece,pieceRow,pieceCol)){
gameOver=true; isPlaying=false;
if(isPerfectGame){ perfectGameOverElement.style.display='flex'; playSound(perfectSound); }
else { finalScoreElement.textContent=score; gameOverElement.style.display='flex'; }
pauseBackgroundMusic();
}
drawNextPiece(); drawBoard();
}
function updateDisplay(){
scoreElement.textContent = score;
}
function resizeCanvas(){
let container = document.querySelector('.game-canvas-container');
if(!container) return;
let cw = container.clientWidth;
let ch = container.clientHeight;
let maxBlockW = Math.floor(cw / COLS);
let maxBlockH = Math.floor(ch / ROWS);
blockSize = Math.min(maxBlockW, maxBlockH, 30);
blockSize = Math.max(blockSize, 10);
gameCanvas.width = COLS * blockSize;
gameCanvas.height = ROWS * blockSize;
drawBoard();
drawNextPiece();
}
function handleNaturalDrop(){
if(!currentPiece||isPaused||!isPlaying) return;
let newRow=pieceRow+1, newCol=pieceCol;
if(!checkCollision(currentPiece,newRow,newCol)){ pieceRow=newRow; pieceCol=newCol; drawBoard(); return true; }
mergePiece(); clearLines(); spawnNewPiece(); return false;
}
function gameLoop(time=0){
let delta=time-lastTime; lastTime=time;
if(isPlaying&&!isPaused&&!gameOver){ dropCounter+=delta; if(dropCounter>dropInterval){ handleNaturalDrop(); dropCounter=0; } }
requestAnimationFrame(gameLoop);
}
function toggleStartPause(){
if(gameOver) { init(); }
if(!isPlaying){
isPlaying=true; isPaused=false;
playBackgroundMusic();
startPauseBtn.innerHTML = '<i class="fas fa-pause"></i> PAUSE';
startPauseBtn.classList.add('pause-style');
} else {
isPaused = !isPaused;
if(isPaused){
startPauseBtn.innerHTML = '<i class="fas fa-play"></i> START';
pauseBackgroundMusic();
} else {
startPauseBtn.innerHTML = '<i class="fas fa-pause"></i> PAUSE';
playBackgroundMusic();
}
}
}
function resetGame(){
init();
isPlaying=false; isPaused=false;
startPauseBtn.innerHTML = '<i class="fas fa-play"></i> START';
startPauseBtn.classList.remove('pause-style');
pauseBackgroundMusic();
}
function zenDecrypt(){ window.open('https://amitofoicu.github.io/home/yiqu.html', '_blank'); }
function handleKeyDown(e){
if(!isPlaying||isPaused) return;
switch(e.key){
case 'ArrowLeft': movePiece(-1,0); e.preventDefault(); break;
case 'ArrowRight': movePiece(1,0); e.preventDefault(); break;
case 'ArrowDown': movePiece(0,1); e.preventDefault(); break;
case 'ArrowUp': rotatePiece(); e.preventDefault(); break;
case ' ': case 'Spacebar': e.preventDefault(); hardDrop(); break;
case 'p': case 'P': toggleStartPause(); e.preventDefault(); break;
case 'm': case 'M': toggleSound(); e.preventDefault(); break;
}
}
function setupEventListeners(){
initAudio();
document.addEventListener('keydown', handleKeyDown);
startPauseBtn.addEventListener('click', toggleStartPause);
soundToggle.addEventListener('click', toggleSound);
restartBtn.addEventListener('click', ()=>{ resetGame(); toggleStartPause(); });
zenDecryptBtn.addEventListener('click', zenDecrypt);
leftBtn.addEventListener('click', ()=>movePiece(-1,0));
rightBtn.addEventListener('click', ()=>movePiece(1,0));
downBtn.addEventListener('click', ()=>movePiece(0,1));
centerBtn.addEventListener('click', rotatePiece);
let holdInterval;
function startHold(dx,dy){
if(dy>0){ hasAccelerated=true; if(isPlaying) isPerfectGame=false; }
movePiece(dx,dy);
holdInterval = setInterval(()=>movePiece(dx,dy),100);
}
function stopHold(){ clearInterval(holdInterval); }
[leftBtn, rightBtn, downBtn].forEach(btn=>{
btn.addEventListener('mousedown',(e)=>{
if(!isPlaying||isPaused) return;
let dx=0, dy=0;
if(btn===leftBtn) dx=-1; else if(btn===rightBtn) dx=1; else if(btn===downBtn) dy=1;
startHold(dx,dy);
});
btn.addEventListener('mouseup', stopHold); btn.addEventListener('mouseleave', stopHold);
btn.addEventListener('touchstart',(e)=>{ e.preventDefault(); if(!isPlaying||isPaused) return; let dx=0,dy=0; if(btn===leftBtn) dx=-1; else if(btn===rightBtn) dx=1; else if(btn===downBtn) dy=1; startHold(dx,dy); });
btn.addEventListener('touchend',(e)=>{ e.preventDefault(); stopHold(); });
});
window.addEventListener('resize', resizeCanvas);
window.addEventListener('orientationchange', ()=>setTimeout(resizeCanvas,100));
document.addEventListener('visibilitychange', ()=>{
if(document.hidden && isPlaying && !isPaused){ isPaused=true; startPauseBtn.innerHTML='<i class="fas fa-play"></i> RESUME'; pauseBackgroundMusic(); }
});
document.addEventListener('click', ()=>{ if(!audioInitialized) initAudio(); }, {once:true});
}
window.addEventListener('load', ()=>{
init();
setupEventListeners();
gameLoop();
setTimeout(() => preloadAllAudio(), 100);
});
window.addEventListener('orientationchange', function() {
setTimeout(resizeCanvas, 300);
});
</script>
</body>
</html>



