欢迎光临
我们一直在努力

使用DeepSeek打磨出的网页游戏 - 俄罗斯方块(国际版)

继之前一款经典俄罗斯方块,近日使用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>

    赞(0)
    未经允许不得转载:171主机测评 » 使用DeepSeek打磨出的网页游戏 - 俄罗斯方块(国际版)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址