diff --git a/script.js b/script.js index 127b174..9c9416a 100644 --- a/script.js +++ b/script.js @@ -1821,44 +1821,70 @@ function handleResize() { const gameContainer = document.querySelector('.game-container'); const gameWrapper = document.querySelector('.game-wrapper'); - if (isMobile) { + if (isMobile || forceMobileControls) { // Scale the canvas to fit mobile screen const viewportWidth = Math.min(window.innerWidth, document.documentElement.clientWidth); const viewportHeight = Math.min(window.innerHeight, document.documentElement.clientHeight); - // Calculate optimal scale while maintaining aspect ratio - const scaleWidth = (viewportWidth * 0.85) / (COLS * BLOCK_SIZE); - const scaleHeight = (viewportHeight * 0.6) / (ROWS * BLOCK_SIZE); - const scale = Math.min(scaleWidth, scaleHeight, 1); // Don't scale up beyond 1 + // Detect orientation + const isPortrait = viewportHeight > viewportWidth; - // Apply scale transform to canvas - canvas.style.transform = `scale(${scale})`; - canvas.style.transformOrigin = 'top left'; + // Calculate available game area (accounting for UI elements) + const titleHeight = 40; // Estimate for title + const scoreHeight = isPortrait ? 120 : 0; // In portrait, score is above/below game + const availableHeight = viewportHeight - titleHeight - scoreHeight; - // Adjust container width based on scaled canvas - if (gameWrapper) { - gameWrapper.style.width = `${(COLS * BLOCK_SIZE) * scale}px`; - gameWrapper.style.height = `${(ROWS * BLOCK_SIZE) * scale}px`; + // For portrait: maximize width, maintain aspect ratio + if (isPortrait) { + // Use 95% of viewport width + const targetWidth = viewportWidth * 0.95; + const targetHeight = (targetWidth / COLS) * ROWS; + + // If height is too tall, scale down + if (targetHeight > availableHeight * 0.9) { + const scaleFactor = (availableHeight * 0.9) / targetHeight; + canvas.style.width = `${targetWidth * scaleFactor}px`; + canvas.style.height = `${targetHeight * scaleFactor}px`; + } else { + canvas.style.width = `${targetWidth}px`; + canvas.style.height = `${targetHeight}px`; + } + } + // For landscape: maximize height, maintain aspect ratio + else { + // Use 80% of available height + const targetHeight = availableHeight * 0.8; + const targetWidth = (targetHeight / ROWS) * COLS; + + // If width is too wide, scale down + if (targetWidth > viewportWidth * 0.6) { + const scaleFactor = (viewportWidth * 0.6) / targetWidth; + canvas.style.width = `${targetWidth * scaleFactor}px`; + canvas.style.height = `${targetHeight * scaleFactor}px`; + } else { + canvas.style.width = `${targetWidth}px`; + canvas.style.height = `${targetHeight}px`; + } } - // Scale next piece preview + // Scale next piece preview to match if (nextPieceCanvas) { + const scale = parseInt(canvas.style.width) / (COLS * BLOCK_SIZE); nextPieceCanvas.style.transform = `scale(${scale})`; nextPieceCanvas.style.transformOrigin = 'top left'; } - // Show touch controls + // Show touch controls mode document.body.classList.add('mobile-mode'); } else { // Reset to desktop layout - canvas.style.transform = 'none'; - if (gameWrapper) { - gameWrapper.style.width = ''; - gameWrapper.style.height = ''; - } + canvas.style.width = ''; + canvas.style.height = ''; + if (nextPieceCanvas) { nextPieceCanvas.style.transform = 'none'; } + document.body.classList.remove('mobile-mode'); } } @@ -2132,6 +2158,10 @@ const SWIPE_THRESHOLD = 30; const TAP_THRESHOLD = 200; // milliseconds const DOUBLE_TAP_THRESHOLD = 300; // milliseconds let lastTapTime = 0; +let lastMoveTime = 0; +let touchIdentifier = null; +let multiTouchDetected = false; +const MOVE_COOLDOWN = 100; // ms between moves to prevent too rapid movement // Initialize touch controls function initTouchControls() { @@ -2149,11 +2179,20 @@ function handleTouchStart(event) { if (gameOver || paused) return; event.preventDefault(); + // Track if multiple touches + if (event.touches.length > 1) { + multiTouchDetected = true; + return; + } + + multiTouchDetected = false; + // Store the initial touch position const touch = event.touches[0]; touchStartX = touch.clientX; touchStartY = touch.clientY; touchStartTime = Date.now(); + touchIdentifier = touch.identifier; } // Handle touch move event @@ -2161,28 +2200,44 @@ function handleTouchMove(event) { if (gameOver || paused) return; event.preventDefault(); + // Skip if it's a multi-touch gesture + if (multiTouchDetected || event.touches.length > 1) return; + + const now = Date.now(); + if (now - lastMoveTime < MOVE_COOLDOWN) return; + if (!event.touches.length) return; const touch = event.touches[0]; + // Make sure we're tracking the same touch + if (touch.identifier !== touchIdentifier) return; + const diffX = touch.clientX - touchStartX; const diffY = touch.clientY - touchStartY; - // Detect horizontal swipe for movement - if (Math.abs(diffX) > SWIPE_THRESHOLD) { - if (diffX > 0) { - p.moveRight(); + // Only process if movement is significant (prevent accidental moves) + const absX = Math.abs(diffX); + const absY = Math.abs(diffY); + + if (absX > SWIPE_THRESHOLD || absY > SWIPE_THRESHOLD) { + // Determine direction of swipe - if more horizontal than vertical + if (absX > absY) { + if (diffX > 0) { + p.moveRight(); + } else { + p.moveLeft(); + } } else { - p.moveLeft(); + // Only handle downward swipes for soft drop + if (diffY > 0) { + p.moveDown(); + } } // Reset touch start to allow for continuous movement touchStartX = touch.clientX; - } - - // Detect downward swipe for soft drop - if (diffY > SWIPE_THRESHOLD) { - p.moveDown(); touchStartY = touch.clientY; + lastMoveTime = now; } } @@ -2191,6 +2246,18 @@ function handleTouchEnd(event) { if (gameOver || paused) return; event.preventDefault(); + // Process two-finger tap + if (multiTouchDetected) { + // Choose either horizontal or vertical 3D rotation + if (Math.random() > 0.5) { + p.rotate3DX(); + } else { + p.rotate3DY(); + } + multiTouchDetected = false; + return; + } + const touchEndTime = Date.now(); const touchDuration = touchEndTime - touchStartTime; @@ -2206,102 +2273,34 @@ function handleTouchEnd(event) { lastTapTime = touchEndTime; } } + + // Reset touch identifier + touchIdentifier = null; } // Create on-screen control buttons for mobile function createTouchControlButtons() { - const controlsContainer = document.createElement('div'); - controlsContainer.className = 'touch-controls-container'; - document.body.appendChild(controlsContainer); + // Do not create buttons - using gesture-based controls only - // Create left button - const leftBtn = document.createElement('button'); - leftBtn.className = 'touch-btn left-btn'; - leftBtn.innerHTML = '←'; - leftBtn.addEventListener('touchstart', function(e) { - e.preventDefault(); - const moveInterval = setInterval(function() { - if (!gameOver && !paused) p.moveLeft(); - }, 100); - - leftBtn.addEventListener('touchend', function() { - clearInterval(moveInterval); - }, { once: true }); - }); + // Create touch instructions overlay + const touchInstructions = document.createElement('div'); + touchInstructions.className = 'touch-instructions'; + touchInstructions.innerHTML = ` +

Swipe left/right: Move piece

+

Swipe down: Soft drop

+

Tap: Rotate right

+

Double tap: Hard drop

+

Two-finger tap: 3D rotate

+ `; + document.body.appendChild(touchInstructions); - // Create right button - const rightBtn = document.createElement('button'); - rightBtn.className = 'touch-btn right-btn'; - rightBtn.innerHTML = '→'; - rightBtn.addEventListener('touchstart', function(e) { - e.preventDefault(); - const moveInterval = setInterval(function() { - if (!gameOver && !paused) p.moveRight(); - }, 100); - - rightBtn.addEventListener('touchend', function() { - clearInterval(moveInterval); - }, { once: true }); - }); - - // Create down button - const downBtn = document.createElement('button'); - downBtn.className = 'touch-btn down-btn'; - downBtn.innerHTML = '↓'; - downBtn.addEventListener('touchstart', function(e) { - e.preventDefault(); - const moveInterval = setInterval(function() { - if (!gameOver && !paused) p.moveDown(); - }, 100); - - downBtn.addEventListener('touchend', function() { - clearInterval(moveInterval); - }, { once: true }); - }); - - // Create rotate button - const rotateBtn = document.createElement('button'); - rotateBtn.className = 'touch-btn rotate-btn'; - rotateBtn.innerHTML = '↻'; - rotateBtn.addEventListener('touchstart', function(e) { - e.preventDefault(); - if (!gameOver && !paused) p.rotate('right'); - }); - - // Create 3D buttons - const rotate3DXBtn = document.createElement('button'); - rotate3DXBtn.className = 'touch-btn rotate3d-x-btn'; - rotate3DXBtn.innerHTML = 'W'; - rotate3DXBtn.addEventListener('touchstart', function(e) { - e.preventDefault(); - if (!gameOver && !paused) p.rotate3DX(); - }); - - const rotate3DYBtn = document.createElement('button'); - rotate3DYBtn.className = 'touch-btn rotate3d-y-btn'; - rotate3DYBtn.innerHTML = 'X'; - rotate3DYBtn.addEventListener('touchstart', function(e) { - e.preventDefault(); - if (!gameOver && !paused) p.rotate3DY(); - }); - - // Create hard drop button - const hardDropBtn = document.createElement('button'); - hardDropBtn.className = 'touch-btn hard-drop-btn'; - hardDropBtn.innerHTML = '⤓'; - hardDropBtn.addEventListener('touchstart', function(e) { - e.preventDefault(); - if (!gameOver && !paused) p.hardDrop(); - }); - - // Add all buttons to the container - controlsContainer.appendChild(leftBtn); - controlsContainer.appendChild(rightBtn); - controlsContainer.appendChild(downBtn); - controlsContainer.appendChild(rotateBtn); - controlsContainer.appendChild(rotate3DXBtn); - controlsContainer.appendChild(rotate3DYBtn); - controlsContainer.appendChild(hardDropBtn); + // Show instructions briefly, then fade out + setTimeout(() => { + touchInstructions.classList.add('fade-out'); + setTimeout(() => { + touchInstructions.style.display = 'none'; + }, 1000); + }, 5000); } // Start the game diff --git a/style.css b/style.css index c9bdbd3..c2681bf 100644 --- a/style.css +++ b/style.css @@ -498,16 +498,17 @@ input[type=range]::-moz-range-thumb { .mobile-mode .game-container { flex-direction: column; align-items: center; - gap: 20px; - padding: 15px; + gap: 15px; + padding: 10px; max-width: 100vw; box-sizing: border-box; - margin-top: 70px; + margin-top: 50px; /* Reduced top margin */ } .mobile-mode .game-title { font-size: 24px; top: 10px; + text-shadow: 0 0 8px rgba(255, 0, 255, 0.7); } .mobile-mode .game-wrapper { @@ -518,11 +519,12 @@ input[type=range]::-moz-range-thumb { } .mobile-mode #next-piece-preview { - position: relative; - top: auto; - left: auto; - margin: 10px auto; - width: 120px; + position: absolute; + top: -50px; /* Position above the game board */ + left: 50%; + transform: translateX(-50%); + margin: 0; + background: rgba(0, 0, 0, 0.7); } .mobile-mode .score-container { @@ -530,10 +532,11 @@ input[type=range]::-moz-range-thumb { display: flex; flex-direction: row; flex-wrap: wrap; - justify-content: space-between; + justify-content: center; align-items: center; - gap: 10px; - padding: 10px; + gap: 8px; + padding: 8px; + background: rgba(0, 0, 0, 0.7); } .mobile-mode .score-container p { @@ -542,7 +545,7 @@ input[type=range]::-moz-range-thumb { } .mobile-mode .game-btn { - margin: 5px; + margin: 3px; padding: 8px 12px; font-size: 10px; } @@ -551,82 +554,70 @@ input[type=range]::-moz-range-thumb { display: none; } -/* Touch control buttons */ -.touch-controls-container { +/* Touch instructions */ +.touch-instructions { display: none; position: fixed; - bottom: 20px; - left: 0; - right: 0; - z-index: 100; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.8); + border: 2px solid rgba(0, 255, 255, 0.7); + border-radius: 10px; + padding: 15px; + z-index: 1000; + box-shadow: 0 0 20px rgba(0, 255, 255, 0.5); + transition: opacity 1s ease; + text-align: center; } -.mobile-mode .touch-controls-container { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 10px; +.mobile-mode .touch-instructions { + display: block; } -.touch-btn { - width: 60px; - height: 60px; - border-radius: 50%; - background: rgba(51, 51, 51, 0.7); - border: 2px solid rgba(0, 255, 255, 0.5); - color: #00ffff; - font-size: 24px; - display: flex; - justify-content: center; - align-items: center; - -webkit-tap-highlight-color: transparent; - user-select: none; - touch-action: manipulation; - box-shadow: 0 0 10px rgba(0, 255, 255, 0.3); +.touch-instructions p { + margin: 10px 0; + font-size: 14px; + color: #fff; } -.touch-btn:active { - background: rgba(0, 255, 255, 0.3); - transform: scale(0.95); -} - -/* Button positioning */ -.left-btn, .right-btn, .down-btn { - position: relative; -} - -.rotate-btn, .rotate3d-x-btn, .rotate3d-y-btn, .hard-drop-btn { - background: rgba(255, 0, 255, 0.2); - border-color: rgba(255, 0, 255, 0.5); - color: #ff00ff; +.touch-instructions.fade-out { + opacity: 0; } /* Portrait vs landscape adjustments */ @media (orientation: portrait) { .mobile-mode .game-container { - padding-top: 60px; + padding-top: 40px; } - .touch-controls-container { - padding: 0 10px 10px; + .mobile-mode canvas#tetris { + width: 90vw; + height: auto; + object-fit: contain; } } -@media (orientation: landscape) and (max-width: 900px) { +@media (orientation: landscape) { .mobile-mode .game-container { flex-direction: row; flex-wrap: wrap; justify-content: center; - margin-top: 50px; + margin-top: 40px; + padding: 5px; } .mobile-mode .score-container { width: auto; - min-width: 200px; + min-width: 150px; + max-width: 30%; + height: 80vh; + overflow-y: auto; } .mobile-mode .game-wrapper { margin: 0; + height: 80vh; } .mobile-mode #next-piece-preview { @@ -634,17 +625,13 @@ input[type=range]::-moz-range-thumb { top: 0; left: 100%; margin-left: 10px; + transform: none; } - .touch-controls-container { - flex-direction: row; - bottom: 10px; - } - - .touch-btn { - width: 50px; - height: 50px; - font-size: 20px; + .mobile-mode canvas#tetris { + height: 80vh; + width: auto; + object-fit: contain; } } @@ -654,12 +641,6 @@ input[type=range]::-moz-range-thumb { font-size: 20px; } - .touch-btn { - width: 50px; - height: 50px; - font-size: 20px; - } - .mobile-mode .score-container p { font-size: 10px; } @@ -670,16 +651,10 @@ input[type=range]::-moz-range-thumb { .mobile-mode .game-container { flex-direction: row; flex-wrap: wrap; - gap: 30px; + gap: 20px; } .mobile-mode .game-title { font-size: 28px; } - - .touch-btn { - width: 70px; - height: 70px; - font-size: 28px; - } } \ No newline at end of file