您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Un modo de juego de Tetris completo para Drawaria.online con gráficos, sonido y partículas generados proceduralmente, ocupando gran parte de la pantalla.
// ==UserScript== // @name Drawaria Tetris Full Game // @namespace http://tampermonkey.net/ // @version 1.0 // @description Un modo de juego de Tetris completo para Drawaria.online con gráficos, sonido y partículas generados proceduralmente, ocupando gran parte de la pantalla. // @author YouTubeDrawaria // @match https://drawaria.online/* // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // @grant none // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=drawaria.online // ==/UserScript== (function() { 'use strict'; // --- 1. CSS Injection for Styling, Animations, and Particles --- const style = document.createElement('style'); style.innerHTML = ` @keyframes glowing-border { 0% { box-shadow: 0 0 5px #00FFFF, 0 0 10px #00FFFF, 0 0 15px #00FFFF, inset 0 0 5px rgba(0, 255, 255, 0.3); } 50% { box-shadow: 0 0 10px #00FFFF, 0 0 20px #00FFFF, 0 0 30px #00FFFF, inset 0 0 8px rgba(0, 255, 255, 0.5); } 100% { box-shadow: 0 0 5px #00FFFF, 0 0 10px #00FFFF, 0 0 15px #00FFFF, inset 0 0 5px rgba(0, 255, 255, 0.3); } } @keyframes title-pulse { 0% { transform: scale(1); text-shadow: 0 0 5px #00FFFF; } 50% { transform: scale(1.03); text-shadow: 0 0 10px #00FFFF, 0 0 20px #00FFFF; } 100% { transform: scale(1); text-shadow: 0 0 5px #00FFFF; } } /* Added for potential hiding of Drawaria elements - EXPERIMENTAL */ #main-game-container, #canvas-container { /* These are common IDs, adjust if Drawaria uses different ones */ display: none !important; /* THIS IS EXPERIMENTAL and might break Drawaria */ } .tetris-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); /* Center the container */ background: #1a1a2e; /* Dark theme */ border: 3px solid #00FFFF; /* Brighter and thicker border */ border-radius: 15px; box-shadow: 0 0 20px #00FFFF, 0 0 30px #00FFFF; /* More intense glow */ animation: glowing-border 2s infinite alternate; /* Pulsating glow */ font-family: 'Press Start 2P', cursive, monospace; /* Pixelated font or similar */ color: #E0E0E0; display: flex; flex-direction: column; overflow: hidden; /* For particles */ z-index: 10000; /* Ensure it's on top */ width: 80%; /* Occupy 80% of viewport width */ max-width: 960px; /* Max width in pixels */ height: 85vh; /* Occupy 85% of viewport height */ max-height: 720px; /* Max height in pixels */ padding: 15px; box-sizing: border-box; /* Include padding and border in element's total width and height */ position: relative; /* For particle positioning */ } .tetris-title { color: #00FFFF; font-size: clamp(2em, 5vw, 3.5em); /* Responsive font size */ text-align: center; margin-bottom: 15px; text-shadow: 0 0 8px #00FFFF, 0 0 15px #00FFFF; animation: title-pulse 3s infinite ease-in-out; } .tetris-game-area { display: flex; justify-content: center; /* Center the game and sidebar */ align-items: flex-start; flex-grow: 1; /* Allow game area to take available space */ gap: 15px; /* Space between canvas and sidebar */ } .tetris-canvas { background: #0d0d1a; /* Even darker background for the play area */ border: 2px solid #00BFFF; /* Slightly different blue border */ box-shadow: inset 0 0 10px rgba(0, 255, 255, 0.5), 0 0 8px rgba(0, 255, 255, 0.3); display: block; /* Remove extra space below canvas */ flex-shrink: 0; /* Prevent canvas from shrinking */ /* Canvas will scale based on BLOCK_SIZE and COLS/ROWS */ } .tetris-sidebar { display: flex; flex-direction: column; align-items: center; padding: 10px; background: #2a2a4a; border-radius: 8px; box-shadow: 0 0 12px rgba(0, 255, 255, 0.4); height: fit-content; /* Sidebar fits its content */ } .tetris-info-box { background: #1d1d3d; border: 1px solid #00BFFF; padding: 10px; margin-bottom: 12px; border-radius: 6px; width: 120px; /* Adjusted width */ text-align: center; box-sizing: border-box; font-size: 0.9em; text-shadow: 0 0 3px rgba(0, 255, 255, 0.5); } .tetris-info-box label { display: block; margin-bottom: 4px; color: #00FFFF; font-size: 0.85em; } .tetris-info-box span { font-weight: bold; color: #F0F0F0; font-size: 1.2em; /* Slightly larger text */ } .tetris-next-piece-display { background: #1d1d3d; border: 1px solid #00BFFF; padding: 8px; margin-bottom: 12px; border-radius: 6px; width: 100px; /* Small fixed width for next piece */ height: 100px; /* Adjust based on block size */ display: flex; justify-content: center; align-items: center; overflow: hidden; box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); } .tetris-next-piece-display canvas { display: block; } .tetris-button { background: linear-gradient(145deg, #00FFFF, #00BFFF); color: #1a1a2e; border: none; padding: 12px 20px; /* Larger buttons */ font-size: 1.1em; font-weight: bold; border-radius: 7px; cursor: pointer; transition: background 0.3s ease, transform 0.1s ease; box-shadow: 0 0 12px rgba(0, 255, 255, 0.7); text-transform: uppercase; letter-spacing: 1px; margin-top: 5px; /* Add some space above buttons */ } .tetris-button:hover { background: linear-gradient(145deg, #00BFFF, #00FFFF); transform: translateY(-3px); /* More pronounced hover effect */ box-shadow: 0 0 18px rgba(0, 255, 255, 0.9); } .tetris-button:active { transform: translateY(0); } .game-over-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.85); /* Slightly more opaque */ display: flex; flex-direction: column; justify-content: center; align-items: center; color: #FF00FF; /* Neon magenta */ font-size: 2.5em; /* Larger game over text */ text-shadow: 0 0 10px #FF00FF, 0 0 20px #FF00FF; text-align: center; border-radius: 15px; /* Match container radius */ z-index: 1000; box-sizing: border-box; } .game-over-overlay h2 { font-size: 1.5em; /* Scale down h2 a bit */ margin-bottom: 15px; } .game-over-overlay p { margin: 10px 0; color: #FFC0CB; /* Pink for score/level text */ font-size: 0.8em; text-shadow: 0 0 5px rgba(255, 192, 203, 0.5); } /* Particle animations */ @keyframes float { 0% { transform: translateY(0px) rotate(0deg) scale(1); opacity: 0.7; } 50% { transform: translateY(-15px) rotate(10deg) scale(1.2); opacity: 1; } 100% { transform: translateY(0px) rotate(0deg) scale(1); opacity: 0.7; } } .tetris-particle { position: absolute; background: rgba(255, 255, 255, 0.5); /* Semi-transparent white */ border-radius: 50%; box-shadow: 0 0 5px rgba(0, 255, 255, 0.7); /* Cyan glow */ opacity: 0; /* Start invisible */ animation-name: float; animation-iteration-count: infinite; animation-timing-function: ease-in-out; pointer-events: none; /* Do not block clicks */ z-index: 0; /* Behind other content */ } `; document.head.appendChild(style); // --- 2. HTML Structure Creation --- const tetrisContainer = document.createElement('div'); tetrisContainer.className = 'tetris-container'; tetrisContainer.innerHTML = ` <h1 class="tetris-title">TETRIS</h1> <div class="tetris-game-area"> <canvas id="tetrisCanvas" class="tetris-canvas"></canvas> <div class="tetris-sidebar"> <div class="tetris-info-box"> <label>SCORE</label> <span id="tetrisScore">0</span> </div> <div class="tetris-info-box"> <label>LEVEL</label> <span id="tetrisLevel">1</span> </div> <div class="tetris-info-box"> <label>LINES</label> <span id="tetrisLines">0</span> </div> <div class="tetris-info-box"> <label>NEXT</label> <div class="tetris-next-piece-display"> <canvas id="nextPieceCanvas"></canvas> </div> </div> <button id="tetrisStartButton" class="tetris-button">Start Game</button> </div> </div> <div id="gameOverOverlay" class="game-over-overlay" style="display: none;"> <h2>GAME OVER!</h2> <p>Score: <span id="finalScore">0</span></p> <p>Level: <span id="finalLevel">1</span></p> <button id="restartButton" class="tetris-button">Play Again?</button> </div> `; document.body.appendChild(tetrisContainer); // Get elements const canvas = document.getElementById('tetrisCanvas'); const ctx = canvas.getContext('2d'); const nextPieceCanvas = document.getElementById('nextPieceCanvas'); const nextCtx = nextPieceCanvas.getContext('2d'); const scoreDisplay = document.getElementById('tetrisScore'); const levelDisplay = document.getElementById('tetrisLevel'); const linesDisplay = document.getElementById('tetrisLines'); const startButton = document.getElementById('tetrisStartButton'); const gameOverOverlay = document.getElementById('gameOverOverlay'); const finalScoreDisplay = document.getElementById('finalScore'); const finalLevelDisplay = document.getElementById('finalLevel'); const restartButton = document.getElementById('restartButton'); // --- 3. Web Audio API for Procedural Sound Generation --- const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); function createOscillator(frequency, type = 'sine', duration = 0.1, volume = 0.5) { if (!audioCtx) return; const oscillator = audioCtx.createOscillator(); const gainNode = audioCtx.createGain(); oscillator.type = type; oscillator.frequency.setValueAtTime(frequency, audioCtx.currentTime); gainNode.gain.setValueAtTime(volume, audioCtx.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration); oscillator.connect(gainNode); gainNode.connect(audioCtx.destination); oscillator.start(); oscillator.stop(audioCtx.currentTime + duration); } function playMoveSound() { createOscillator(260, 'triangle', 0.05, 0.2); } function playRotateSound() { createOscillator(440, 'square', 0.08, 0.3); createOscillator(550, 'square', 0.08, 0.3); } function playHardDropSound() { createOscillator(180, 'sawtooth', 0.1, 0.5); createOscillator(90, 'square', 0.1, 0.6); } function playLineClearSound() { const baseFreq = 600; for (let i = 0; i < 4; i++) { createOscillator(baseFreq + (i * 100), 'sine', 0.08, 0.4); } } function playGameOverSound() { createOscillator(100, 'square', 0.5, 0.7); createOscillator(50, 'sawtooth', 0.5, 0.8); setTimeout(() => { createOscillator(200, 'triangle', 0.3, 0.5); }, 300); } function playStartupMelody() { const notes = [ { freq: 440, dur: 0.15 }, { freq: 523, dur: 0.15 }, { freq: 659, dur: 0.15 }, { freq: 880, dur: 0.3 } ]; let currentTime = audioCtx.currentTime; notes.forEach(note => { const oscillator = audioCtx.createOscillator(); const gainNode = audioCtx.createGain(); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(note.freq, currentTime); gainNode.gain.setValueAtTime(0.3, currentTime); gainNode.gain.exponentialRampToValueAtTime(0.001, currentTime + note.dur); oscillator.connect(gainNode); gainNode.connect(audioCtx.destination); oscillator.start(currentTime); oscillator.stop(currentTime + note.dur); currentTime += note.dur; }); } // --- 4. Tetris Game Logic --- const COLS = 10; const ROWS = 20; let BLOCK_SIZE; // Will be determined by canvas size and aspect ratio // Function to resize canvas and adjust block size dynamically function resizeGame() { const containerWidth = tetrisContainer.offsetWidth; const containerHeight = tetrisContainer.offsetHeight; // Calculate available space for game area excluding sidebar and title const sidebarWidth = 250; // Approximate sidebar width const titleHeight = 70; // Approximate title height const gameAreaMaxWidth = containerWidth * 0.75 - sidebarWidth; // Max width for canvas const gameAreaMaxHeight = containerHeight - titleHeight - 30; // Max height for canvas // Determine block size based on available space and aspect ratio const aspectRatio = COLS / ROWS; let calculatedBlockSizeWidth = gameAreaMaxWidth / COLS; let calculatedBlockSizeHeight = gameAreaMaxHeight / ROWS; BLOCK_SIZE = Math.min(calculatedBlockSizeWidth, calculatedBlockSizeHeight); // Ensure BLOCK_SIZE is at least 10px and not too large BLOCK_SIZE = Math.max(10, Math.min(BLOCK_SIZE, 30)); // Min 10px, Max 30px canvas.width = COLS * BLOCK_SIZE; canvas.height = ROWS * BLOCK_SIZE; nextPieceCanvas.width = 4 * BLOCK_SIZE; // For next piece display nextPieceCanvas.height = 4 * BLOCK_SIZE; // Adjust sidebar to align with canvas height const sidebar = tetrisContainer.querySelector('.tetris-sidebar'); if (sidebar) { // sidebar.style.height = `${canvas.height}px`; // This might make sidebar too short if game area is constrained by width } // Redraw everything if game is running if (gameStarted && !gameOver) { drawBoard(); drawCurrentPiece(); drawNextPiece(); } else if (!gameStarted) { drawBoard(); // Draw initial empty board if not started drawNextPiece(); // Draw initial empty next piece } } // Event listener for window resize window.addEventListener('resize', resizeGame); let board = []; let currentPiece; let nextPiece; let score = 0; let level = 1; let lines = 0; let gameOver = false; let dropInterval = 1000; let lastDropTime = 0; let gameStarted = false; const TETROMINOS = { 'I': { shape: [[0,0,0,0], [1,1,1,1], [0,0,0,0], [0,0,0,0]], color: '#00FFFF' }, 'J': { shape: [[1,0,0], [1,1,1], [0,0,0]], color: '#0000FF' }, 'L': { shape: [[0,0,1], [1,1,1], [0,0,0]], color: '#FFA500' }, 'O': { shape: [[1,1], [1,1]], color: '#FFFF00' }, 'S': { shape: [[0,1,1], [1,1,0], [0,0,0]], color: '#00FF00' }, 'T': { shape: [[0,1,0], [1,1,1], [0,0,0]], color: '#800080' }, 'Z': { shape: [[1,1,0], [0,1,1], [0,0,0]], color: '#FF0000' } }; function initBoard() { board = Array(ROWS).fill(null).map(() => Array(COLS).fill(0)); score = 0; level = 1; lines = 0; gameOver = false; dropInterval = 1000; updateUI(); generateNewPiece(); generateNewPiece(true); // Generate next piece gameOverOverlay.style.display = 'none'; startButton.textContent = 'Restart Game'; resizeGame(); // Initialize canvas size } function updateUI() { scoreDisplay.textContent = score; levelDisplay.textContent = level; linesDisplay.textContent = lines; } function generateNewPiece(isNext = false) { const pieceNames = Object.keys(TETROMINOS); const randomName = pieceNames[Math.floor(Math.random() * pieceNames.length)]; const newPiece = JSON.parse(JSON.stringify(TETROMINOS[randomName])); newPiece.x = Math.floor(COLS / 2) - Math.floor(newPiece.shape[0].length / 2); newPiece.y = 0; if (isNext) { nextPiece = newPiece; drawNextPiece(); } else { currentPiece = newPiece; if (checkCollision(currentPiece.x, currentPiece.y, currentPiece.shape)) { endGame(); } } } function drawBlock(context, x, y, color, blockSize) { if (color === 0) return; context.fillStyle = color; context.fillRect(x * blockSize, y * blockSize, blockSize, blockSize); // Add procedural highlights and shadows for depth const highlightColor = 'rgba(255, 255, 255, 0.6)'; const shadowColor = 'rgba(0, 0, 0, 0.4)'; context.strokeStyle = highlightColor; context.lineWidth = 1; context.beginPath(); context.moveTo(x * blockSize, y * blockSize); context.lineTo((x + 1) * blockSize -1, y * blockSize); // Slightly offset to avoid drawing over next block's border context.lineTo((x + 1) * blockSize -1, (y + 1) * blockSize -1); context.stroke(); context.strokeStyle = shadowColor; context.beginPath(); context.moveTo(x * blockSize, (y + 1) * blockSize -1); context.lineTo((x + 1) * blockSize -1, (y + 1) * blockSize -1); context.lineTo((x + 1) * blockSize -1, y * blockSize); context.stroke(); } function drawBoard() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let row = 0; row < ROWS; row++) { for (let col = 0; col < COLS; col++) { drawBlock(ctx, col, row, board[row][col], BLOCK_SIZE); } } } function drawCurrentPiece() { if (!currentPiece) return; for (let row = 0; row < currentPiece.shape.length; row++) { for (let col = 0; col < currentPiece.shape[row].length; col++) { if (currentPiece.shape[row][col]) { drawBlock(ctx, currentPiece.x + col, currentPiece.y + row, currentPiece.color, BLOCK_SIZE); } } } } function drawNextPiece() { nextCtx.clearRect(0, 0, nextPieceCanvas.width, nextPieceCanvas.height); if (!nextPiece) return; const pieceWidth = nextPiece.shape[0].length; const pieceHeight = nextPiece.shape.length; const startX = (4 - pieceWidth) / 2; // Center in 4x4 grid const startY = (4 - pieceHeight) / 2; // Center in 4x4 grid for (let row = 0; row < pieceHeight; row++) { for (let col = 0; col < pieceWidth; col++) { if (nextPiece.shape[row][col]) { drawBlock(nextCtx, startX + col, startY + row, nextPiece.color, BLOCK_SIZE); } } } } function checkCollision(x, y, shape) { for (let row = 0; row < shape.length; row++) { for (let col = 0; col < shape[row].length; col++) { if (shape[row][col]) { const boardX = x + col; const boardY = y + row; if (boardX < 0 || boardX >= COLS || boardY >= ROWS) { return true; } if (boardY >= 0 && board[boardY][boardX] !== 0) { return true; } } } } return false; } function mergePieceToBoard() { for (let row = 0; row < currentPiece.shape.length; row++) { for (let col = 0; col < currentPiece.shape[row].length; col++) { if (currentPiece.shape[row][col]) { const boardX = currentPiece.x + col; const boardY = currentPiece.y + row; if (boardY >= 0) { board[boardY][boardX] = currentPiece.color; } } } } } function clearLines() { let linesClearedThisTurn = 0; for (let row = ROWS - 1; row >= 0; row--) { if (board[row].every(cell => cell !== 0)) { board.splice(row, 1); board.unshift(Array(COLS).fill(0)); linesClearedThisTurn++; playLineClearSound(); row++; // Check the new line at this row index again } } if (linesClearedThisTurn > 0) { lines += linesClearedThisTurn; if (linesClearedThisTurn === 1) score += 100 * level; else if (linesClearedThisTurn === 2) score += 300 * level; else if (linesClearedThisTurn === 3) score += 500 * level; else if (linesClearedThisTurn === 4) score += 800 * level; const oldLevel = level; level = Math.floor(lines / 10) + 1; if (level > oldLevel) { dropInterval *= 0.8; if (dropInterval < 100) dropInterval = 100; } updateUI(); } } function rotatePiece(piece) { const originalShape = piece.shape; const newShape = originalShape[0].map((val, index) => originalShape.map(row => row[index]).reverse()); if (!checkCollision(piece.x, piece.y, newShape)) { piece.shape = newShape; playRotateSound(); return true; } const kicks = [[-1, 0], [1, 0], [-2, 0], [2, 0], [0, -1]]; // Basic wall kicks for (const [dx, dy] of kicks) { if (!checkCollision(piece.x + dx, piece.y + dy, newShape)) { piece.x += dx; piece.y += dy; piece.shape = newShape; playRotateSound(); return true; } } return false; } function dropPiece() { if (gameOver) return; if (!checkCollision(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) { currentPiece.y++; } else { mergePieceToBoard(); clearLines(); currentPiece = nextPiece; generateNewPiece(true); if (checkCollision(currentPiece.x, currentPiece.y, currentPiece.shape)) { endGame(); } } } function hardDrop() { if (gameOver) return; while (!checkCollision(currentPiece.x, currentPiece.y + 1, currentPiece.shape)) { currentPiece.y++; } mergePieceToBoard(); playHardDropSound(); clearLines(); currentPiece = nextPiece; generateNewPiece(true); if (checkCollision(currentPiece.x, currentPiece.y, currentPiece.shape)) { endGame(); } } function endGame() { gameOver = true; playGameOverSound(); finalScoreDisplay.textContent = score; finalLevelDisplay.textContent = level; gameOverOverlay.style.display = 'flex'; gameStarted = false; } function gameLoop(currentTime) { if (gameOver || !gameStarted) { requestAnimationFrame(gameLoop); return; } const deltaTime = currentTime - lastDropTime; if (deltaTime > dropInterval) { dropPiece(); lastDropTime = currentTime; } drawBoard(); drawCurrentPiece(); requestAnimationFrame(gameLoop); } // --- 5. Input Handling --- document.addEventListener('keydown', (e) => { // Only process input if the Tetris container is focused or if it's a game-critical key // This is tricky. For now, let's assume we want Tetris input to always work if gameStarted. if (gameOver || !gameStarted) return; switch (e.key) { case 'ArrowLeft': e.preventDefault(); currentPiece.x--; if (checkCollision(currentPiece.x, currentPiece.y, currentPiece.shape)) currentPiece.x++; else playMoveSound(); break; case 'ArrowRight': e.preventDefault(); currentPiece.x++; if (checkCollision(currentPiece.x, currentPiece.y, currentPiece.shape)) currentPiece.x--; else playMoveSound(); break; case 'ArrowDown': e.preventDefault(); currentPiece.y++; if (checkCollision(currentPiece.x, currentPiece.y, currentPiece.shape)) { currentPiece.y--; dropPiece(); } else { playMoveSound(); score += 1; } lastDropTime = performance.now(); break; case 'ArrowUp': e.preventDefault(); rotatePiece(currentPiece); break; case 'x': e.preventDefault(); rotatePiece(currentPiece); break; case ' ': e.preventDefault(); hardDrop(); break; } drawBoard(); drawCurrentPiece(); }); // --- 6. Particle System --- const particles = []; const NUM_PARTICLES = 20; // Increased number of particles function createParticle() { const particle = document.createElement('div'); particle.className = 'tetris-particle'; tetrisContainer.appendChild(particle); const size = Math.random() * 4 + 3; // 3-7px particle.style.width = `${size}px`; particle.style.height = `${size}px`; particle.style.left = `${Math.random() * 100}%`; particle.style.top = `${Math.random() * 100}%`; particle.style.animationDuration = `${Math.random() * 6 + 6}s`; // 6-12s animation particle.style.animationDelay = `${Math.random() * -6}s`; particle.style.opacity = Math.random() * 0.3 + 0.6; // 0.6 - 0.9 opacity particles.push(particle); } for (let i = 0; i < NUM_PARTICLES; i++) { createParticle(); } // --- 7. Game Initialization and Start Button Logic --- function startGame() { if (!gameStarted) { playStartupMelody(); initBoard(); gameStarted = true; lastDropTime = performance.now(); requestAnimationFrame(gameLoop); } } startButton.addEventListener('click', startGame); restartButton.addEventListener('click', startGame); // Initial setup initBoard(); // Sets up board and UI elements initially resizeGame(); // Adjust canvas size on load })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址